在 C++ 中,对具有唯一 ID 的对象进行复制/移动/分配的正确方法是啥?

Posted

技术标签:

【中文标题】在 C++ 中,对具有唯一 ID 的对象进行复制/移动/分配的正确方法是啥?【英文标题】:In C++, what is the proper way to do copy/move/assignment for objects with a unique id?在 C++ 中,对具有唯一 ID 的对象进行复制/移动/分配的正确方法是什么? 【发布时间】:2021-02-14 17:33:42 【问题描述】:
/* 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 之外的此类的所有属性。如果我添加另一个属性,则必须编辑调用代码(在多个位置)才能设置该新属性。

    是否有一种好方法可以允许编辑除唯一 ID 之外的所有属性? 是否可以重载赋值并移动运算符来执行此操作并仍然删除复制构造函数? 谁有一个具有唯一 ID 的类的好例子?

【问题讨论】:

这听起来像XY Problem。 std::unordered_map<unique_id, Example> 似乎是另一种选择,然后您就不需要 Example 类中的任何 unique_id 内容。 使用您在类中显示的数据成员,您无需显式重载移动构造函数或移动赋值运算符。将它们声明为default。复制函数可以声明为delete 以防止复制。 我建议你少考虑类属性,多考虑行为。让需要的类成员由实现行为的需要来决定。 你的代码不是已经演示了如何完成#1吗?您提供了成员函数来设置除unique_id 之外的每个属性。这允许编辑除唯一 ID 之外的所有属性。为什么这不适合您的真正目标? 将您的唯一 ID 和相关功能封装到一个专用类中,该类具有定制的构造函数、赋值运算符等以及访问器但没有设置器(以防止暴露 ID 数据以供外部代码修改)。然后,该类可以是具有其他属性的类的基类或成员。然后编译器生成的复制/移动构造函数、赋值运算符等将根据需要运行。或者,如果需要,您可以将 Example 类设为单例。 【参考方案1】:

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

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

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

类可以移动但不可复制是可以的。此类类型称为 move-only。

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

谁有一个具有唯一 ID 的类的好例子?

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

【讨论】:

【参考方案2】:

如果每个对象都具有唯一的永久标识,那么您可能无法真正支持复制——对象的副本应该与原始对象相同,在这种情况下,您是说每个对象都应该是唯一的,所以有一个副本可能没有意义。

另一方面,移动构造应该更合理。尽管 ID 从一个对象移动到另一个对象,但在给定时间仍然只有一个具有给定标识的对象。

move ctor 非常简单明了。只需像往常一样在成员初始化器列表中初始化您的成员,包括由于它是移动 ctor,您希望从源中的 unique_id 移动:

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

理论上,您可能应该在attribute1attribute2 上使用std::move,但由于它们是ints,因此通常不会有任何实际区别。

然后我们得到一个有点不重要的:移动分配。由于我们将unique_id 定义为const,因此我们不能直接复制它。为此,我们首先销毁目标对象的当前内容,然后使用placement new 进行从源到目标的移动构造:

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 bstd::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 成员的人,他们使用相同的销毁/放置新“技巧”来完成分配),但我没有足够的野心来查看它们以检查是否有任何与此完全相同的。

【讨论】:

以上是关于在 C++ 中,对具有唯一 ID 的对象进行复制/移动/分配的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

C ++生成唯一ID [重复]

稍微深入点理解C++复制控制转

熊猫重新索引仅对具有唯一值的索引对象有效

如何根据具有命名空间的唯一元素或 ID 对元素进行排序和分组

Pandas - 重新索引仅对具有唯一值的索引对象有效

C++ 将对象向量中的元素复制到具有此元素的向量中