复制 ctor 和赋值运算符中是不是存在语义稍有不同的问题?

Posted

技术标签:

【中文标题】复制 ctor 和赋值运算符中是不是存在语义稍有不同的问题?【英文标题】:Any gotchas in copy ctor and assignment operator having slightly different semantics?复制 ctor 和赋值运算符中是否存在语义稍有不同的问题? 【发布时间】:2011-01-13 16:11:16 【问题描述】:

请看下面的代码,告诉我以后会不会出问题,如果会,如何避免。

class Note

   int id;
   std::string text;

public:
   // ... some ctors here...

   Note(const Note& other) : id(other.id), text(other.text) 

   void operator=(const Note& other) // returns void: no chaining wanted
   
      if (&other == this) return;
      text = other.text;  
      // NB: id stays the same!    
   
   ...
;

简而言之,我希望复制构造函数创建对象的精确副本,包括其(数据库)ID 字段。另一方面,当我分配时,我只想复制数据字段。 但我有一些担忧,因为通常复制 ctor 和 operator= 具有相同的语义。

id 字段仅供 Note 及其朋友使用。对于所有其他客户端,赋值运算符确实会创建一个精确的副本。用例:当我想编辑一个笔记时,我使用复制ctor创建一个副本,编辑它,然后在管理笔记的笔记本类上调用保存:

 Note n(notebook.getNote(id));
 n = editNote(n); // pass by const ref (for the case edit is canceled)
 notebook.saveNote(n);

另一方面,当我想创建一个与现有笔记内容相同的全新笔记时,我可以这样做:

 Note n; 
 n = notebook.getNote(id); 
 n.setText("This is a copy");
 notebook.addNote(n);

这种方法可行吗?如果不是,请指出可能的负面后果是什么!非常感谢!

【问题讨论】:

【参考方案1】:

如果您想要的语义与赋值运算符的预期不匹配,请不要使用它。相反,通过声明私有 operator= 来禁用它,并定义一个函数,其名称可以明确发生了什么,例如 copyDataFields

【讨论】:

是否可以有一个公共副本 ctor 但私有 op=? @Alex:是的。这意味着一旦你有一个完全初始化的Note,它就不能被分配。如果它想要数据,它将使用copyDataFields 方法。它仍然可以构造成匹配另一个音符:Note othernote(firstNote);【参考方案2】:

虽然这可能适用于您的具体情况,但我一般不会推荐它。

诸如 STL 之类的库希望复制构造函数和赋值运算符能够“像他们应该做的那样”工作。如果您违反了 C++ 语义,那么您可能会发现对象的 STL 容器无法正常工作。 STL 将在不同的情况下调用您的复制构造函数和赋值运算符,具体取决于容器。

当您的代码没有按照您的想法执行时,很容易完全混淆。

【讨论】:

【参考方案3】:

从技术上讲,它是可行的,并且在技术上可行,但我不会那样做。 我看到的问题是:

    您更改了 C++ 群体已知的赋值运算符的“自然”语义。

    复制构造和赋值这两个孪生操作由于语义不同而不一致。

    该解决方案容易出错,因为即使看起来像是赋值,也很容易意外调用复制构造函数。如果程序员这样写你的第二个用例:

    Note n = notebook.getNote(id);
    

    然后调用复制构造函数,不是赋值,所以你得到n作为与预期不同的对象。

为什么不让你的意图清晰明确:

Note& Notebook::editNote(int id);
Note  Notebook::createNote(int id);

【讨论】:

我不想直接暴露一个Note进行编辑,我想要一种“复制、编辑、替换原始”的行为。 我几乎看不出“复制、编辑、替换原件”和“获取原件、编辑”之间有什么区别

以上是关于复制 ctor 和赋值运算符中是不是存在语义稍有不同的问题?的主要内容,如果未能解决你的问题,请参考以下文章

依赖项没有复制 ctor 或赋值运算符时的 C++ 初始化程序列表

删除copy-ctor和copy-assignment - public、private还是protected?

Ruby 如何在语义上处理赋值?

派生自std :: exception的类的赋值运算符

C++ 中类的默认成员函数的问题(构造函数、析构函数、运算符 =、复制构造函数)(默认 ctor、dtor、复制 ctor)

调用复制 ctor 而不是移动 ctor - 编译器可以发出警告吗?