在 C++ 中,对具有唯一 ID 的对象进行复制/移动/赋值的正确方法是什么?

In C++, what is the proper way to do copy/move/assignment for objects with a unique id?

提问人:Alex 提问时间:11/2/2020 更新时间:11/2/2020 访问量:400

问:

/* Example.h file */
class Example {

  public:
    Example(const std::string& unique_id_, int attribute1_, int attribute2_,): 
      unique_id(unique_id_), attribute1(attribute1_), attribute2(attribute2_){};

    void set_attribute1(int attribute1_){ attribute1 = attribute1_; }
    void set_attribute2(int attribute2_){ attribute2 = attribute2_; }

    /* Deleting copy/move/assignment operators to make each instance unique */
    Exercise_Data(const Exercise_Data&) = delete;
    Exercise_Data(Exercise_Data&&) = delete;
    Exercise_Data& operator= (Exercise_Data&) = delete;
    Exercise_Data& operator= (Exercise_Data&&) = delete;

    private:
      const std::string unique_id;
      int attribute1;
      int attribute2;
}

目前,这是我的代码。在调用代码的多个位置,我希望能够编辑此类的所有属性,但unique_id除外。如果我添加另一个属性,则必须编辑调用代码(在多个位置)才能设置该新属性。

  1. 有没有一种好方法可以允许编辑除唯一 ID 之外的所有属性?
  2. 是否可以重载赋值和移动运算符来执行此操作,同时仍然删除复制构造函数?
  3. 有人有一个很好的具有唯一 ID 的类的例子吗?
C++ 复制构造函数 move-semantics uniqueidentifier 赋值运算符

评论

4赞 PaulMcKenzie 11/2/2020
这听起来像是一个 XY 问题。A 似乎是另一种选择,然后你就不需要课堂上的任何这些东西了。std::unordered_map<unique_id, Example>unique_idExample
0赞 Some programmer dude 11/2/2020
使用类中显示的数据成员,无需显式重载移动构造函数或移动赋值运算符。将它们声明为 。复制功能可以声明为防止复制。defaultdelete
0赞 Some programmer dude 11/2/2020
我建议你少考虑类属性,多考虑行为。让所需的类成员由实现行为的需要决定。
0赞 JaMiT 11/2/2020
您的代码不是已经演示了如何完成 #1 吗?您提供了成员函数来设置除 之外的每个属性。这允许编辑除唯一 ID 之外的所有属性。为什么这不足以实现您的真正目标?unique_id
0赞 Peter 11/2/2020
将您的唯一 ID 和相关功能封装到一个专用类中,该类具有定制的构造函数、赋值运算符等以及访问器,但没有 setter(以防止公开 ID 数据以供外部代码修改)。然后,该类可以是基类,也可以是具有其他属性的类的成员。然后,编译器生成的复制/移动构造函数、赋值运算符等将根据需要运行。或者,如果需要,可以将类设置为单例。Example

答:

0赞 eerorika 11/2/2020 #1

有没有一种好方法可以允许编辑除唯一 ID 之外的所有属性?

只要确保 id 是私有的,并且您永远不会返回对它的非常量指针/引用。确保成员函数不修改 id 应该是微不足道的。

是否可以重载赋值和移动运算符来执行此操作,同时仍然删除复制构造函数?

类可以移动,但不可复制。此类类型称为仅移动。

如果要保留成员 const,则无法实现移动操作。我建议不要使用const成员。

有人有一个很好的具有唯一 ID 的类的例子吗?

std::unique_ptr本质上是此类的一个例子。“id”表示析构函数清理的一些唯一资源。

0赞 Jerry Coffin 11/2/2020 #2

如果每个对象都有一个唯一的、永久的身份,那么你可能就不能真正支持复制了——一个对象的副本应该与原始对象相同,在这种情况下,你说每个对象都应该是唯一的,所以拥有一个副本可能没有意义。

另一方面,搬家建设应该更合理。尽管 ID 从一个对象移动到另一个对象,但在给定时间仍然只有一个对象具有给定的身份。

移动 ctor 非常简单明了。只需像往常一样在成员初始值设定项列表中初始化您的成员,包括由于它是一个移动 ctor,因此您希望从源代码中的unique_id移动:

Example(Example&& other) 
  : unique_id(std::move(other.unique_id)) 
  , attribute1(other.attribute1)
  , attribute2(other.attribute2)  
{ 
}

从理论上讲,您可能也应该使用 on 和 ,但由于它们是 s,因此通常不会产生任何真正的区别。std::moveattribute1attribute2int

然后我们进入一个有点不平凡的问题:移动任务。既然我们已经定义为 ,我们就不能只是复制它。为了完成这项工作,我们首先销毁目标对象的当前内容,然后使用 placement new 执行从源到目标的移动构造:unique_idconst

Example &operator=(Example&& other) { 
 this->~Example();
 new (this) Example(std::move(other));
 return *this;
}

这之所以有效,是因为资格认证在破坏或建造过程中没有影响,所以这基本上是我们实现任务的唯一方法。const

下面是一个演示其全部操作的示例:

#include <string>
#include <new>
#include <iostream>

class Example {

  public:
    Example(const std::string& unique_id_, int attribute1_, int attribute2_): 
      unique_id(unique_id_), attribute1(attribute1_), attribute2(attribute2_){};

    void set_attribute1(int attribute1_){ attribute1 = attribute1_; }
    void set_attribute2(int attribute2_){ attribute2 = attribute2_; }

    /* Deleting copy/move/assignment operators to make each instance unique */
    Example(const Example&) = delete;
    Example& operator= (Example&) = delete;
    
    Example(Example&& other) 
      : unique_id(std::move(other.unique_id)) 
      , attribute1(other.attribute1)
      , attribute2(other.attribute2)  
    { 
    }

    Example &operator=(Example&& other) { 
     this->~Example();
     new (this) Example(std::move(other));
     return *this;
    }

    private:
      const std::string unique_id;
      int attribute1;
      int attribute2;

    friend std::ostream &operator<<(std::ostream &os, Example const &e) { 
      return os << "ID: " << e.unique_id;
    }
};

int main() { 
  Example a("A", 1, 2);

  std::cout << "A: " << a << "\n";

  Example b{std::move(a)}; // move construction

  std::cout << "B: " << b << "\n";

  Example c("B", 3, 4);   // construct a destination object
  c = std::move(b);       // move assign into it

  std::cout << "C: " << c << "\n";
}

至于对象中唯一 ID 的真实示例......如果不回去搜索代码,我不确定。我能记得一些看起来它们可能至少非常接近的(当然还有其他有 const 成员的,它们使用相同的销毁/放置新“技巧”来执行任务),但我没有足够的野心来浏览它们以检查是否有任何与此完全相同的内容。