参考抽象类分配问题

时间:2011-09-08 07:44:31

标签: c++ reference abstract-class

关于问题“reference to abstract class”,我写了以下例子:

#include <iostream>
#include <vector>

class data
{
  public:
    virtual int get_value() = 0;
};

class data_impl : public data
{
  public:
    data_impl( int value_ ) : value( value_ ) {}
    virtual int get_value() { return value; }
  private:
    int value;
};

class database
{
  public:
    data& get( int index ) { return *v[index]; }
    void  add( data* d ) { v.push_back( d ); }
  private:
    std::vector< data* > v;
};

int main()
{
    data_impl d1( 3 );
    data_impl d2( 7 );

    database db;
    db.add( &d1 );
    db.add( &d2 );

    data& d = db.get( 0 );
    std::cout << d.get_value() << std::endl;
    d = db.get( 1 );
    std::cout << d.get_value() << std::endl;

    data& d_ = db.get( 1 );
    std::cout << d_.get_value() << std::endl;
    d_ = db.get( 0 );
    std::cout << d_.get_value() << std::endl;

    return 0;
}

令我惊讶的是,示例打印出来:

3
3
7
7

看起来参考作业的作用与我的期望不同。我希望:

3
7
7
3

请指出我的错误是什么?

谢谢!

6 个答案:

答案 0 :(得分:4)

data& d = db.get( 0 );
std::cout << d.get_value() << std::endl;
d = db.get( 1 );
std::cout << d.get_value() << std::endl;

第一个语句是引用初始化,而第三个语句是slicing assignment

您无法重新安排参考资料。

干杯&amp;第h。,

答案 1 :(得分:2)

原因是,不能重新分配引用。

您已将引用d分配给db中的第一个元素:

data& d = db.get( 0 );

后来你试图重新分配它:

d = db.get( 1 );

但是,这不会更改引用本身,而是更改引用指向的值。

但是,在这种情况下,引用是一个不包含数据的抽象基类。所以作业并没有改变任何东西。

实际上,你在db中打印第一个元素两次,然后再打印第二个元素两次。

答案 2 :(得分:2)

我将稍微简化一下这个例子:

data_impl a(3), b(7);
data &ra(a);
data &rb(b);
std::cout << ra.get_value() << std::endl;
ra = rb;                                  // [1]
std::cout << ra.get_value() << std::endl;

现在使用简化的代码可以更容易地推断出该程序。您获取ra子对象的引用a,但引用的类型为data,而不是data_impl。与rbb类似。在标有[1]的行中,您正在执行从rbra的赋值,表达式的两个参数的静态类型为data,这意味着无论什么他们引用的真实对象是,该特定行仅分配data子对象。这称为切片

也就是说,[1]将data a子对象设置为与data b子对象相同。由于data不包含任何实际数据,因此a保持不变。

有关更具说明性的示例,您可以向data添加一个字段,并检查该表达式是否会修改a中的该字段,即使它没有修改派生类中的字段。 / p>

然后你可以手动尝试一个工具operator=作为一个虚函数,并检查你是否可以获得预期的结果,但是实现会有点麻烦,因为C ++中的成员函数没有协变参数,这意味着所有级别的operator=签名都将({const})引用data,并且您将被迫向下转换(验证转换是否成功),然后执行赋值...

答案 3 :(得分:1)

问题可以在这里看到:

data& d = /**/;

d = /**/;        <---

此处,d的静态类型为data&,这意味着它实际上是语法糖:

d.operator=(/**/);

由于data::operator=不是虚拟的,因此会调用它。不派遣派生类。由于data是纯接口,因此它没有任何属性,因此代码没有意义。


问题是,这里没有真正的解决方案:

  • 使operator=虚拟是一团糟,因为参数必须是data const&中的data,以后不能在后续派生类中更改,这意味着类型安全性丢失
  • 使用setValue / getValue虚拟对只有在派生类没有比通过此接口提供的数据更多的数据时才会起作用,这是一个不合理的假设

由于问题无法解决,唯一的出路是使诊断更容易:在基类中禁用复制和分配。

有两种方法可以做到这一点:

  • 使整个层次结构不可复制(从boost::noncopyabledelete继承复制运算符和复制赋值运算符等等。)

  • 使当前类不可复制,但允许通过复制构造函数和赋值运算符protected自动复制派生类。

通常,非final类具有公共拷贝构造函数和赋值运算符是错误的(我想知道是否有任何编译器对此进行诊断)。

答案 4 :(得分:0)

以下行可能不是您的想法:     数据&安培; d = db.get(0);     std :: cout&lt;&lt; d.get_value()&lt;&lt;的std :: ENDL;     d = db.get(1);

第三行,d不会成为对第二个数据库条目的引用。它仍然是第一个参考。引用只能在创建时初始化。

答案 5 :(得分:0)

您无法将引用与引用分开。 enter link description here

与指针不同,一旦引用绑定到对象,它就不能“重新”到另一个对象。引用本身不是一个对象(它没有标识;获取引用的地址会给你引用的地址;记住:引用是它的引用)。

最奇怪的是编译器不会发出警告。

我尝试使用gcc -Wall -pedantic