Я пытаюсь переписать объект, на который указывает указатель на абстрактный класс, с объектом, полученным из этого класса. Однако значение не меняется. Если я делаю то же самое с указателем на не-абстрактный класс, все работает по назначению.
Я предполагаю, что компилятор отказывается перезаписывать память, потому что не знает точного размера объекта, который записывается во время компиляции.
Есть ли способ, который позволяет мне перезаписать объект?
Ниже приведен фрагмент кода, чтобы продемонстрировать, что я имею в виду. Он тестируется с использованием 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;
}
Давайте рассмотрим шаг за шагом, что происходит.
Сначала вы определили указатель на 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
Примечание. Мы не рассматриваем утечки памяти в вашем примере. Мы рассматриваем эффект применения операторов присваивания.
x
только если его динамический тип является подходящим.
Ближайшим компилятором, эквивалентным вашему рабочему коду, может быть следующее:
*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"); }
if (...) {x = new A("A");} else {x = new B("B");}
что будет самым производным объектом?
*x
- это A
а во второй - B
*(new A(....))
. Когда-либо. Это утечка памяти без цели. Ближайший правильный код будет*a = A("B");