Проблема с передачей указателя строки от дочернего к родительскому

Я пытаюсь передать указатель на const char * от дочернего к родительскому, но явно не понимаю, как это сделать правильно. Вот код, который содержит три класса: 1) родитель 2) Альфа (ребенок) 3_ Бета (дочерняя)

#pragma once
class Parent {
protected:
    char *childName;

public:
    Parent ( char* _childName ) {
        childName = _childName;
    }

    char *getChildName () {
        return childName;
    }
};

class Alpha: public Parent {
protected:
    const char* alphaName = "ALPHA";

public:
    Alpha (): Parent ( alphaName ) {
        Serial.print ( F ( "My name is " ) );
        Serial.println ( getChildName () );
    }
};

class Beta: public Parent {
protected:
    const char* betaName = "BETA";

public:
    Beta (): Parent ( betaName ) {
        Serial.print ( F ( "My name is " ) );
        Serial.println ( getChildName () );
    }
};

Alpha *alpha;
Beta *beta;
void setup () {
    Serial.begin ( 115200 );
    while (!Serial.availableForWrite ()) {}

    alpha = new Alpha ();
    beta = new Beta ();
}

void loop() {}

И вот что я получаю: Меня зовут �

Меня зовут ˵

, 👍1

Обсуждение

попробуйте добавить const ко всем char*. на какой архитектуре mcu вы его компилируете и запускаете?, @Juraj

Я попробовал ваш код после добавления отсутствующего const для его компиляции и получил ожидаемый результат: «Меня зовут АЛЬФА\r\nМеня зовут БЕТА\r\n»., @Edgar Bonet

Давным-давно старший программист сказал мне, что правильный способ определения литералов состоит в том, чтобы сделать const char betaName[] = "BETA";, к сожалению, я не могу вспомнить точных деталей, но это было связано с тем, как была организована память. выделено. * ОДНАКО * это было еще в 90-х, так что, надеюсь, сейчас компиляторы лучше., @Code Gorilla

Короткая версия заключается в том, что const побуждает компилятор использовать память программы (которой обычно больше) для хранения значения, а не память данных (которой обычно меньше). Заметили все «определения», которые я использовал? Это связано с тем, что разные комбинации компиляторов и платформ, скорее всего, будут давать разные результаты., @st2000


1 ответ


Лучший ответ:

2

Проблема в том, что alphaName и betaName являются переменными-членами. Это означает, что они инициализируются во время конструкторов Alpha и Beta. Однако конструктор суперкласса Parent всегда вызывается первым, до вызова конструкторов дочернего класса. Результат: Parent::Parent(char *) вызывается с неинициализированным указателем.

Вот ваш код в Compiler Explorer: https://godbolt.org/z/otAuQU Как видите, Alpha::Alpha() компилируется в следующее:

call Parent::Parent(char*)
ldd r24,Y+1
ldd r25,Y+2
ldi r18,lo8(.LC0)
ldi r19,hi8(.LC0)

.LC0 — это строка "АЛЬФА".

Решение здесь состоит в том, чтобы сделать имена статическими. Таким образом, они инициализируются до вызова родительского конструктора.

class Parent {
  protected:
    const char *const childName;

  public:
    Parent(const char* childName) : childName(childName) {}

    const char *getChildName () const {
        return childName;
    }
};

class Alpha : public Parent {
  protected:
    constexpr static const char *alphaName = "ALPHA";

  public:
    Alpha() : Parent(alphaName) {
        Serial.print(F("My name is "));
        Serial.println(getChildName());
    }
};

Компилируется в:

ldi r22,lo8(.LC0)
ldi r23,hi8(.LC0)
call Parent::Parent(char const*)

Как уже упоминалось, указатели на строковые литералы всегда должны быть константными. Единственная причина, по которой это не дает ошибки, заключается в том, что разработчики Arduino решили, что было бы неплохо скомпилировать все с помощью -fpermissive ...
Изменение строкового литерала является поведением undefined, поэтому возникает ошибка.

,

И награда достается тттапе, который своим точным и правильно работающим образцом кода поджарил несколько оставшихся клеток моего мозга. Спасибо, тттапа! И теперь у меня много кода, который нужно исправить., @Bob Jones

@BobJones, я думаю, что использования const char* должно быть достаточно, и Эдгар Бонет сообщил, что это работает в комментарии. Но мы не знаем, какую платформу вы используете., @Juraj

@Juraj, я думаю, что это все еще неопределенное поведение. Причина, по которой он работает, заключается в том, что компилятор оптимизирует его, потому что знает, что он будет постоянным. Однако нет никакой гарантии, что указатель должен быть инициализирован до вызова родительского конструктора, это просто счастливое совпадение., @tttapa

Ссылка на C++ «Перед началом выполнения составного оператора, формирующего тело функции конструктора, завершается инициализация всех прямых баз, виртуальных баз и нестатических элементов данных». https://en.cppreference.com/w/cpp/language/initializer_list, @Juraj

@Juraj, я не говорю о теле конструктора, я говорю о родительском конструкторе. Сначала вызывается конструктор Parent::Parent(const char *), инициализируются переменные-члены Parent (childName), затем инициализируются элементы данных Alpha (alphaName), и, наконец, выполняется тело Alpha::Alpha(). Я только что протестировал исходный код с добавленной константой на UNO с версией 1.6.23 AVR Core. Это не работает. Когда имена статичны, это работает., @tttapa