参考抽象类分配问题

发布于 2024-12-03 15:44:39 字数 1227 浏览 2 评论 0原文

关于问题“对抽象类的引用”我写了以下示例:

#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

你能指出我的错误是什么吗?

谢谢你!

In relation to question "reference to abstract class" I wrote the following example:

#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;
}

To my surprise, the example prints:

3
3
7
7

and it looks like the reference assignment does the work differently from my expectation. I would expect:

3
7
7
3

Could you please point what my mistake is?

Thank you!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

悲欢浪云 2024-12-10 15:44:39

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

一个语句是引用初始化,而第三个语句是切片赋值

您无法重新设置参考。

干杯&呵呵,,

In

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

the first statement is a reference initialization, while the third statement is a slicing assignment.

You can not re-seat a reference.

Cheers & hth.,

不如归去 2024-12-10 15:44:39

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

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

data& d = db.get( 0 );

然后您尝试重新分配它:

d = db.get( 1 );

但是,这不会更改引用本身,而是更改参考点的值

然而,在这种情况下,引用是对不包含数据的抽象基类的引用。所以任务并没有改变任何东西。

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

The reason is, references can't be reassigned.

You assigned the reference d to the first element in db:

data& d = db.get( 0 );

Then later you tried to reassign it:

d = db.get( 1 );

However, this doesn't change the reference itself, instead it changes the value the reference points to.

However, in this case the reference is to an abstract base class which contains no data. So the assignment isn't changing anything.

In effect you print the first element in db twice, then the 2nd element again twice.

笑忘罢 2024-12-10 15:44:39

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

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;

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

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

作为更具说明性的示例,您可以向 data 添加一个字段,并检查表达式是否修改了 a 中的该字段,即使它没有修改导出中的字段类。

然后你可以尝试手动实现 operator= 作为虚函数,并检查是否可以获得预期的结果,但实现会有点混乱,因为 C++ 中的成员函数没有协变参数,这意味着各级 operator= 的签名将采用对 data 的(const)引用,并且您将被迫进行向下转型(验证转型是否成功)然后执行任务...

I am going to simplify the example a bit:

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;

Now with that simplified code it is easier to reason about the program. You obtain a reference ra to the a subobject, but the reference is of type data, rather than data_impl. Similarly with rb and b. In the line marked with [1] you are performing an assignment from rb to ra, the static type of the two arguments of the expression is data, and that means that regardless of what the real object they refer to is, that particular line is assigning only the data subobject. This is called slicing.

That is, [1] is setting the data subobject of a to be the same as the data subobject of b. Because data does not contain any actual data, the result is that a remains unmodified.

For a more illustrative example, you can add a field to data, and check that the expression does modify that field in a, even if it does not modified the fields in derived classes.

Then you can try an implement operator= manually as a virtual function and check that you can get the expected result, but the implementation will be a bit messier, as there are no covariant arguments to member functions in C++, which means that the signature of operator= at all levels will take a (const) reference to data, and you will be forced to downcast (verify that the cast succeeded) and then perform the assignment...

£冰雨忧蓝° 2024-12-10 15:44:39

问题可以在这里看到:

data& d = /**/;

d = /**/;        <---

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

d.operator=(/**/);

Since data::operator = 不是virtual,那么就调用了。不分派到派生类。而且由于data是一个纯接口,它没有任何属性,所以代码没有任何意义。


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

  • operator= 设为虚拟是一团糟,因为参数必须是 datadata const& >,并且以后不能在后续的派生类中更改,这意味着类型安全性的损失
  • 使用 setValue/getValue 虚拟对仅在派生类不包含更多内容时才有效数据比他们通过这个接口呈现的数据要多,这是不合理的假设

既然问题无法解决,唯一的出路就是让诊断变得更容易:在基类中禁用复制和赋值。

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

  • 使整个层次结构不可复制(继承自boost::noncopyable删除复制运算符和复制赋值运算符,等等...)

  • 使当前类不可复制,但允许派生类自动复制,方法是将复制构造函数和赋值运算符设置为受保护。

一般来说,非最终类拥有公共复制构造函数和赋值运算符是一个错误(我想知道是否有编译器对此进行诊断)。

The problem can be seen here:

data& d = /**/;

d = /**/;        <---

Here, the static type of d is data&, meaning that it is in fact syntactic sugar for:

d.operator=(/**/);

Since data::operator= is not virtual, then it is called. No dispatch to a derived class. And since data is a pure interface, it does not have any attribute, so the code is meaningless.


The problem is, there are no real solution here:

  • Making operator= virtual is a mess, because the parameter must be a data const& in data, and cannot later be changed in subsequent derived classes, meaning a loss of type safety
  • Using a setValue/getValue virtual pair will only work if the derived classes do not hold more data than they present via this interface, which is an unreasonnable assumption

Since the problem cannot be solved, the only way out is to make diagnosis easier: disabling copy and assignment in the base class.

There are two ways to do this:

  • Make the whole hierarch non-copyable (either inherit from boost::noncopyable or delete the copy operator and copy assignment operator, etc...)

  • Make the current class non-copyable, but allow derived classes to be copyable automatically, by making the copy constructor and assignment operator protected.

In general, it is an error for a non-final class to have a public copy constructor and assignment operator (I wonder if any compiler diagnose this).

朦胧时间 2024-12-10 15:44:39

以下几行可能不是您所想的:
数据& d = db.get( 0 );
std::cout << d.get_value() << std::endl;
d = db.get( 1 );

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

Following lines might not be what you think:
data& d = db.get( 0 );
std::cout << d.get_value() << std::endl;
d = db.get( 1 );

the third line, d does not become a reference to the second database entry. It is still a reference to the first. References can only be initialized on creation.

找个人就嫁了吧 2024-12-10 15:44:39

您无法将引用与所指对象分开。 在此处输入链接描述

与指针不同,一旦引用绑定到对象,它不能“重新安置”到另一个对象。引用本身不是一个对象(它没有标识;获取引用的地址即可得到所指对象的地址;记住:引用是其所指对象)。

最奇怪的是编译器不警告它。

我尝试使用 gcc -Wall -pedantic

You can't separate the reference from the referent. enter link description here

Unlike a pointer, once a reference is bound to an object, it can not be "reseated" to another object. The reference itself isn't an object (it has no identity; taking the address of a reference gives you the address of the referent; remember: the reference is its referent).

The strangest thing is the compiler don't warn it.

I try with gcc -Wall -pedantic

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文