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

0

Я пытаюсь переписать объект, на который указывает указатель на абстрактный класс, с объектом, полученным из этого класса. Однако значение не меняется. Если я делаю то же самое с указателем на не-абстрактный класс, все работает по назначению.

Я предполагаю, что компилятор отказывается перезаписывать память, потому что не знает точного размера объекта, который записывается во время компиляции.

Есть ли способ, который позволяет мне перезаписать объект?

Ниже приведен фрагмент кода, чтобы продемонстрировать, что я имею в виду. Он тестируется с использованием g++ (Ubuntu/Linaro 4.8.1-10ubuntu9) и clang (3.2-7ubuntu1).

#include <stdio.h>

class Abs {
public:
    virtual const char *toString() = 0;
};

class A: public Abs {
public:
    A(const char *s): st(s) {};
    const char *toString() {return st;};
private:
    const char *st;
};

class B: public Abs {
public:
    B(const char *s): st(s) {};
    const char *toString() {return st;};
private:
    const char *st;
};

int main(void) {
    //works:
    A *a = new A("A");
    printf("a: %s\n", a->toString());
    //output: A

    *a = *(new A("B"));
    printf("a: %s\n", a->toString());
    //output: B

    //doesn't work:
    Abs *x;

    x = new A("A");
    printf("x: %s\n", x->toString());
    //output: A

    *x = *(new A("B"));
    printf("x: %s\n", x->toString());
    //output: A

    *x = *(new B("C"));
    printf("x: %s\n", x->toString());
    //output: A

    Abs *y = new A("D");
    *x = *y;
    printf("x: %s\n", x->toString());
    //output: A

    return 0;
}
  • 0
    Никогда не пиши *(new A(....)) . Когда-либо. Это утечка памяти без цели. Ближайший правильный код будет *a = A("B");
  • 1
    Я знаю, что это просто для упражнений и любопытства, но ваш код теряет память, как сумасшедший.
Показать ещё 2 комментария
Теги:
pointers
abstract-class

2 ответа

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

Давайте рассмотрим шаг за шагом, что происходит.

Сначала вы определили указатель на A и инициализировали его по адресу объекта типа A, выделенного в кучу

A *a = new A("A");
printf("a: %s\n", a->toString());
//output: A

Затем вы назначили новый выделенный объект объекту, на который указывает

*a = *(new A("B"));
printf("a: %s\n", a->toString());

В этом случае вызывается неявно определяемый оператором присваивания копии компилятора для класса A, а элемент данных st просто копируется из одного объекта в другой.

Поэтому printf выводит строковый литерал "B".

Теперь вы указали указатель на Abs

Abs *x;

и инициализировал его

x = new A("A");
printf("x: %s\n", x->toString());
//output: A

Учтите, что статический тип x равен Abs.

Тогда во всех этих утверждениях

*x = *(new A("B"));
printf("x: %s\n", x->toString());
//output: A

*x = *(new B("C"));
printf("x: %s\n", x->toString());
//output: A

Abs *y = new A("D");
*x = *y;
printf("x: %s\n", x->toString());
//output: A

вызывается оператор присваивания копий, неявно определяемый компилятором для класса Abs. Класс Abs не имеет элемента данных st. Поэтому он не изменяется, потому что во всех этих случаях вызывается оператор назначения копирования для класса Abs, потому что статический тип x равен Abs.

Я хотел бы добавить, что даже в этом утверждении, которое действительно

*x = *(new B("C"));

вызывается оператор назначения копирования класса Abs. В этом случае объект типа B, выделенный в куче, неявно преобразуется в тип Abs.

Если вы хотите, например, что после утверждения

*x = *(new A("B"));

printf выводит "B", тогда вы должны написать

*static_cast<A *>( x ) = *(new A("B"));

В этом случае будет вызываться оператор назначения копирования для класса A.

Также вы можете написать оператор присваивания виртуальной копии в абстрактном классе. Например

class Abs {
public:
    virtual const char *toString() = 0;
    virtual Abs & operator =( const Abd & ) { return *this; }
};

и переопределить его, например, в классе А

class A: public Abs {
public:
    A(const char *s): st(s) {};
    const char *toString() {return st;};
    A & operator =( const Abs &rhs ) { st = static_cast<const A &>( rhs ).st; return *this; }
private:
    const char *st;
};

В этом случае после утверждения

*x = *(new A("B"));

следующий оператор выведет "B"

printf("x: %s\n", x->toString());
//output: B

Примечание. Мы не рассматриваем утечки памяти в вашем примере. Мы рассматриваем эффект применения операторов присваивания.

  • 0
    это проясняет, что происходит. Означает ли это, что невозможно перезаписать абстрактный объект так, как я хочу? Я знаю, что @Kerrek SB дал обходной путь, но он включает динамическое приведение и перезаписывает x только если его динамический тип является подходящим.
  • 0
    @ До S Если вы имеете в виду регистр * x = * (новый A ("B")); тогда вам следует указатель static_case x на указатель типа A.
Показать ещё 3 комментария
0

Ближайшим компилятором, эквивалентным вашему рабочему коду, может быть следующее:

*static_cast<A*>(x) = *new A("A");

Обратите внимание, что следующее неопределенное поведение:

*static_cast<B*>(x) = *new B("A");

Это связано с тем, что наиболее производный объект имеет тип A а не B Здесь более безопасный подход:

if (A * p = dynamic_cast<A*>(x)) { *p = *new A("A"); }
  • 0
    что именно означает большинство производных? если у меня есть следующий код: if (...) {x = new A("A");} else {x = new B("B");} что будет самым производным объектом?
  • 0
    @TillS: Это совершенно другая ситуация - в первой ветви самый производный объект *x - это A а во второй - B
Показать ещё 7 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню