我对如何解决以下问题感到很困惑(在SO和谷歌搜索都没有多大帮助)。
假设我们有一个定义基本运算符的类和一个简单的向量作为数据,并且只在继承的类中添加其他方法:
class Foo
{
public:
// this only copies the data
Foo& operator=(const Foo& foo);
// do something that computes a new Foo from *this
Foo modifiedFoo();
//..
private:
std::vector<int> data;
}
class Bar: public Foo
{
public:
void someNewMethod();
//..
// no new data
}
继承现在确保operator {在bar1 = bar2
做正确的事情。
但从数据的角度来看,Bar
和Foo
基本相同,所以我希望能够同时编写
Foo foo;
Bar bar;
foo = bar; // this ...
bar = foo; // .. and this;
,更具体地说是
bar = foo.modifiedFoo();
[ 编辑: 顺便说一句,这显然不起作用......
bar1 = bar2.modifiedFoo();
我认为这就像在Bar& operator=(const Foo & foo)
中添加另一个Bar
一样容易,但不知怎的,这会被忽略(我不喜欢这样,如果我派生出越来越多的类怎么办?)
那么正确的方法是什么?
谢谢!对不起,如果之前有人问过这个问题。
答案 0 :(得分:4)
这称为切片问题:
foo = bar;
基本上,条形对象就像它只是一个Foo一样使用 然后将bar的Foo部分复制到foo对象上(从而切掉任何知道它的条形码)。
因为编译器会自动为类定义赋值运算符。 以下内容无效:
bar = foo;
这里编译器看到bar的类型为“Bar”并查找赋值运算符。它发现编译器在'Bar'中生成一个并尝试应用它(现在隐藏了Foo中的那个)。编译器生成的赋值运算符如下所示:
Bar& operator=(Bar const& rhs)
因此上述行不匹配,分配失败。
答案 1 :(得分:1)
Martin told you what went wrong,这是你应该做的事情:
Bar bar;
Foo& foo = bar; // this works now
现在foo
是一个对象的 引用 ,你可以让基类引用(和指针,BTW)引用派生类的对象。
但是,这个
bar = foo;
将永远不会工作(至少不是隐含的),它不应该。你在这里要做的是将基类对象(或引用)分配给派生类 但是,虽然派生类总是可以代表基类对象,但事实恰恰相反。 (当你需要的是 船 时,你不能盲目地使用任何 车辆 ,因为不是所有的车辆是船,您正在使用的车辆可能是 汽车 而且您已经淹死了。)
这并不是说你不能让它发挥作用。如果您知道如何从基类对象创建派生类对象,则可以编写一个函数来执行此操作。只是编译器不知道如何从车辆制作汽车。
作为solipist has shown,这样的转换函数可以是构造函数。但是,我会明确转换构造函数:
class Bar: public Foo
{
public:
explicit Bar(const Foo&);
//
}
explicit
关键字确保编译器永远不会尝试调用此构造函数,除非您这样说。如果你删除它,那么这段代码将编译:
//void f(Foo); // you meant to call this, but forgot to include its header
void f(Bar); // you didn't even know this was included
Foo foo;
f(foo); // won't compile with the above Bar class
并且编译器将默默生成此代码:f(Bar(foo));
虽然这一开始看起来很方便,但我认为我迟早会被我允许爬进我的代码中的每一次隐式转换所困扰,并且不得不在以后删除它们。很多年前,我发誓不再允许他们了
请注意,即使使用explicit
转换构造函数,您仍然可以使用f(Bar)
对象调用Foo
- 您只需明确说明:
f(Bar(foo)); // compiles fine even with the explicit conversion constructor
答案 2 :(得分:0)
如果您希望能够将Foo
隐式转换为Bar
,请创建一个非显式构造函数。
class Bar: public Foo
{
public:
Bar(const Foo&);
//
}
现在当你写'bar = foo'时,Foo
被隐式地转换为临时Bar
,因此可以成功使用Bar
赋值运算符。