这是一个基本的概念问题。如果我有一个从 Base 继承的 Derived 类,并且我实例化了一个新的 Derived 对象,我是否可以将其 Base 对象设置为我选择的特定 Base 对象,以便所有调用基类方法都重定向到这个特定的 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;
}
我敢肯定我在那个简单的代码中犯了各种各样的错误,因为我的技能水平很低,但只是为了这个想法。
这在C++可能吗?我们可以从对象中剥离派生的信息,那么我们可以分离和替换继承链中的组件吗?
我为什么要这样做?
考虑一下混合百合:(再次,原谅语法错误(
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);
在这种情况下,如果我们可以将 mixin 的基本对象设置为任何所需的对象,我们就可以在 mixin 中使用带有任何参数列表的构造函数,而 mixin 不需要知道任何关于它的信息。此外,mixin 可以像装饰器一样使用,而无需装饰器之间的通用接口。
感谢您的回答。由于我们可以切掉对象的派生部分,因此基本信息和派生信息似乎是分开存在的。有人可以对此发表评论吗?我们是否可以访问一些内部表,例如我经常听到的 vtables(我对此类东西一无所知,所以也许这不适用(,并完成此操作?
@Benoît
你能解释一下为什么只有 1 和 4 有效,而 2 和 3 不起作用吗? 类基础 { 保护: 标准::字符串名称; 公共: 基数(标准::字符串 n( { 名称 = 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 << "nn";
Derived d2 = Derived("original 2");
d2.doSomething();
d2.printX();
Base b2 = Base("new 2");
static_cast<Base>(d2) = b2;
d2.doSomething();
d2.printX();
cout << "nn";
Derived d3("original 3");
d3.doSomething();
d3.printX();
Base b3("new 3");
static_cast<Base>(d3) = b3;
d3.doSomething();
d3.printX();
cout << "nn";
Derived d4("original 4");
d4.doSomething();
d4.printX();
Base b4("new 4");
*static_cast<Base*>(&d4) = *&b4;
d4.doSomething();
d4.printX();
cout << "nn";
这将打印:
原创 1x = 5新 1x = 6
原创 2x = 5原创 2x = 6
原创 3x = 5原创 3x = 6
原创 4x = 5新 4x = 6
为什么这只适用于使用指针时?
我不是在质疑你为什么要这样做,但除非你的继承破坏了 ISA 关系,否则这样做是完全安全的(例如 Derived 是 Base 的一个受限子集,例如正方形不是矩形,因为只能调整矩形的一个维度的大小,但不可能用正方形这样做(。
*static_cast<Base*>(d) = *rB;
(参考资料(
或者你可以写一个小函数(你会发现很多函数都是这样做的(:
template<typename T>
T& assign(T& to, const T& from)
{
return to = from;
}
assign<Base>(*d, *rB);
无论如何,每次重载/重新定义运算符时都这样做=
Derived& operator=(const Derived& other)
{
// prettier than the cast notation
Base::operator=(other);
// do something specific to Derived;
this->name += " (assigned)";
return *this;
}
No.如果需要这样做,则应使用组合,而不是继承。
(我正在回答更一般的问题 - 在您的特定情况下,您只想更改字符串,您可以从派生类中更改name
(
继承 = IS A
关系。
组成=HAS A
关系。
您正在描述一个类,该类具有 A 类型的对象,您可以在其中设置其实例。
所以你需要使用组合 - 创建一个包含类型 A 成员的类
继承是类型的属性,而不是对象的属性。类型"派生"继承自类型">基础"。你可以制作类型为"Derived"(Derived x;
(的对象,你也可以制作类型为"Base"的对象(Base y
,除非这是被禁止的(,但这些对象中的每一个都是完整的、成熟的对象,没有"可替换的部分"。
继承的要点是,您可以将类型为"Derived"的对象视为"Base"类型的对象(只要通过引用或指针引用它(,也就是说,如果您有函数void foo(Base & b);
,则可以调用foo(x)
。但是foo
只能访问从"基地"继承的x
部分!
你可以将继承和组合结合起来:
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
}
不,你不能那样做。
您(至少(有几个选择:
- 创建一个新的
Derived
对象,由要组合的两个对象的参数混合进行参数化。 - 在
Base
类中创建一些 setter 方法,这些方法允许您在其生存期后期更改其参数/状态。
No.
你为什么要这样做? 如果两个派生应该具有完全相同的 Base,以便通过一个派生的修改显示在另一个派生中,则需要在每个派生中都有某种指向 Base 的指针,从而形成这种组合,而不是继承。
如果要更改派生 Base 中的数据,请编写一个函数来执行此操作,有点像 Base 的复制赋值运算符。
或者,您可以为派生创建一个新的构造函数,该构造函数将采用 Base 和 Derived,并从 Base 获取 Base 信息,并从 Derived 获取 Derived 信息。