установить базовый объект производного объекта?

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

что-то вроде этого:

class Base
{
protected:
    string name;
public:
    Base(string n) { name = n}
    void doSomething(){cout << name << "\n";}
};

class Derived : public Base
{
public:
     Derived(string n) : Base(n) {}

int main()
{
    Derived* d = new Derived("original base"); //create a derived
    d->doSomething(); // prints "original base"
    Base* rB = new Base("replacement base"); // create a new base object
    ((Base*) d) = rB; // replace the base object of d with a new one (pretend code)
    d->doSomething(); // prints "replacement base"

    return 0;
}

Я уверен, что сделал множество ошибок в этом простом коде, потому что мой уровень навыков низок, но только для идеи.

Возможно ли это в С++? Мы можем отделить производную информацию от объекта, поэтому можем ли мы разделить и заменить компоненты в цепочке наследования?

Зачем мне это делать?

Рассмотрим миксиновые лилии: (опять же, простите за синтаксические ошибки)

template <class T> class MyMixin : public T
{
public:
    MyMixin(T desiredBaseObject)
    { 
        // do something to set the desired base 
        // object of this class to desiredBaseObject.
    }
};

RandomClass1 dog(int i = 0);  
RandomClass2 cat(double i = 0.0);
MyMixin< RandomClass1 > mixin1(dog);
MyMixin< RandomClass2 > mixin2(cat);

В этом случае, если бы мы могли установить базовый объект миксина на любой желаемый объект, мы могли бы использовать конструкторы с любым списком параметров в нашем миксине, при этом миксину не нужно было бы ничего об этом знать. Кроме того, миксин можно использовать как декоратор без необходимости использования общего интерфейса среди декораторов.

Спасибо за ответы. Поскольку мы можем отрезать производную часть объекта, кажется, что базовая и производная информация живут отдельно. Может ли кто-нибудь прокомментировать это? Можем ли мы получить доступ к какой-то внутренней таблице, такой как виртуальные таблицы, о которых я так много слышал (я ничего не знаю об этом типе вещей, так что, возможно, это неприменимо), и выполнить это?

@Бенуа

Не могли бы вы объяснить, почему работают только 1 и 4, а 2 и 3 нет? class Base { protected: std::string name; общественность: Base(std::string n) { name = n; }

    virtual void doSomething()
    {
        cout << name << "\n";
    }
};

class Derived : public Base
{
public:
    int x;
    Derived(std::string n) : Base(n)
    {
        x = 5;
    }

    void printX()
    {
        cout << "x = " << x << "\n";
        x++;
    }
};


Derived* d1 = new Derived("original 1");
d1->doSomething();
d1->printX();
Base* rb1 = new Base("new 1");
*static_cast<Base*>(d1) = *rb1;
d1->doSomething();
d1->printX();
cout << "\n\n";

Derived d2 = Derived("original 2");
d2.doSomething();
d2.printX();
Base b2 = Base("new 2");
static_cast<Base>(d2) = b2;
d2.doSomething();
d2.printX();
cout << "\n\n";

Derived d3("original 3");
d3.doSomething();
d3.printX();
Base b3("new 3");
static_cast<Base>(d3) = b3;
d3.doSomething();
d3.printX();
cout << "\n\n";

Derived d4("original 4");
d4.doSomething();
d4.printX();
Base b4("new 4");
*static_cast<Base*>(&d4) = *&b4;
d4.doSomething();
d4.printX();
cout << "\n\n";

это напечатает:

оригинал 1 х = 5 новый 1 х = 6

исходное 2 х = 5 исходное 2 х = 6

Исходное 3 х = 5 Исходное 3 х = 6

исходный 4 х = 5 новый 4 х = 6

Почему это работает только при использовании указателя?


person user487100    schedule 21.06.2011    source источник
comment
Просто для ясности: когда люди говорят, что вы не можете этого сделать, они имеют в виду, что язык говорит, что вы не можете, а не компилятор вам не позволит. Вероятно, есть способ исказить приведения типов, чтобы они компилировались и, возможно, даже работали, но результаты формально не определены.   -  person Dennis Zickefoose    schedule 22.06.2011


Ответы (7)


Я не задаюсь вопросом, почему вы хотите это сделать, но это совершенно безопасно, если только ваше наследование не нарушает отношения ISA (например, производное является ограниченным подмножеством базы, например, квадрат не является прямоугольником, поскольку можно изменить размер только одного размер прямоугольника, но невозможно сделать это с квадратом).

*static_cast<Base*>(d) = *rB;

(работает также со ссылками)

или вы можете написать небольшую функцию (вы найдете много функций, делающих это):

template<typename T>
T& assign(T& to, const T& from)
{
  return to = from;
}

assign<Base>(*d, *rB);

и вообще, вы делаете это каждый раз, когда перегружаете/переопределяете operator=

Derived& operator=(const Derived& other)
{
  // prettier than the cast notation
  Base::operator=(other);

  // do something specific to Derived;
  this->name += " (assigned)";

  return *this;
}
person Benoît    schedule 21.06.2011
comment
Кроме того, почему это не сработает для квадрата, унаследованного от прямоугольника? В чем разница? Не будут ли по-прежнему заменены методы и значения базового класса? Не могли бы вы объяснить некоторые конкретные случаи, в которых это не сработает? - person user487100; 22.06.2011
comment
Пример «Квадрат/Прямоугольник» — это случай, когда он работает технически, но не подходит семантически. - person Benoît; 22.06.2011
comment
О вашем редактировании: static_cast‹Base›(d3) = b3; создает временную Базу, инициализированную из d3 (это строительный слепок), поэтому d3 не модифицируется. Там он работает только с указателем или ссылкой, потому что тогда вы действительно работаете с d3. В качестве альтернативы вы можете написать уродливую d3.Base::operator=(b3). Или используйте небольшую функцию, такую ​​как assign‹›, которую я вам показал. - person Benoît; 22.06.2011

Нет. Если вам нужно это сделать, вы должны использовать композицию, а не наследование.

(Я отвечаю на более общий вопрос - в вашем конкретном случае, когда вы просто хотите изменить строку, вы можете просто изменить name из производного класса)

person Billy ONeal    schedule 21.06.2011

Наследование = IS A отношение.

Состав = HAS A отношение.

Вы описываете класс, который имеет объект типа A, где вы можете установить его экземпляр.
Итак, вам нужно использовать композицию — создайте класс, содержащий член типа A

person Yochai Timmer    schedule 21.06.2011

Наследование — это свойство типов, а не объектов. Тип "Производный" наследуется от типа "Базовый". Вы можете создавать объекты типа "Производный" (Derived x;), а также вы можете создавать объекты типа "Основной" (Base y, если это не запрещено), но каждый из этих объектов является законченным, полноценным объекты без «заменяемых частей».

Суть наследования типов заключается в том, что вы можете обращаться с объектом типа "Производный" как если бы он был объектом типа "Базовый" (при условии, что вы ссылаетесь на него по ссылке или указателю), т.е. то есть, если у вас есть функция void foo(Base & b);, вы можете вызвать foo(x). Но foo может получить доступ только к тем частям x, которые унаследованы от «Базы»!

person Kerrek SB    schedule 21.06.2011

Вы можете комбинировать наследование и композицию:

class A {
    string name;
public: A(const char* s) : name(string(s)) {}
        virtual void m() { cout << name << endl; }
};

class B : A {
public:
    B(const char* s) : A(s), a(0) {}
    void m() { if (a) a->m(); else A::m(); }
    A* a;
};

int main() {
    B b("b");
    b.m(); // prints b
    b.a = new A("a");
    b.m(); // prints a
}
person Nick    schedule 21.06.2011
comment
это верно и круто, но требует, чтобы B преобладал над методами A. - person user487100; 22.06.2011

Нет, вы не можете этого сделать.

У вас есть (по крайней мере) несколько вариантов:

  • Создайте новый объект Derived, параметризованный смесью параметров из двух объектов, которые вы хотите объединить.
  • Создайте несколько методов установки в классе Base, которые позволят вам изменить его параметры/состояние позже в его жизни.
person Oliver Charlesworth    schedule 21.06.2011

No.

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

Если вы хотите изменить данные в Derived's Base, напишите для этого функцию, немного похожую на оператор присваивания копирования для Base.

В качестве альтернативы вы можете создать новый конструктор для Derived, который будет принимать Base и Derived и получать базовую информацию из Base, а Derived информацию из Derived.

person David Thornley    schedule 21.06.2011