C++ 我是否应该在“复制指定”操作符中使用“新放置”

C++ 我是否应该在“复制指定”操作符中使用“新放置”,c++,placement-new,copy-assignment,C++,Placement New,Copy Assignment,我正在创造一个歧视性的工会阶层。我使用C++ 17,所以我可以在技术上使用 STD::变体< /C>,但是由于具体的用例,我希望每个变体的含义更加明确(特别是因为两个案例中除了它们是哪种情况之外,没有数据)。该类看起来像这样(为了简单起见,我将忽略问题中的移动语义): 我对复制赋值操作符的第一个想法是 MyC& MyC::operator &(const MyC& other) { this->~MyC(); _kind = other._kind; i

我正在创造一个歧视性的工会阶层。我使用C++ 17,所以我可以在技术上使用<代码> STD::变体< /C>,但是由于具体的用例,我希望每个变体的含义更加明确(特别是因为两个案例中除了它们是哪种情况之外,没有数据)。该类看起来像这样(为了简单起见,我将忽略问题中的移动语义):

我对复制赋值操作符的第一个想法是

MyC& MyC::operator &(const MyC& other) {
  this->~MyC();
  _kind = other._kind;
  if (_kind == Kind::A) new (&_aVal) string(other.aVal);
  else if (_kind == Kind::B) _bVal = other.bVal;
  else _noVal = other.noVal;
  return *this;
}

这似乎对我来说很好,但我想知道是否更好的C++风格调用字符串的复制赋值操作符,这将需要更多类似的东西:

MyC& MyC::operator &(const MyC& other) {
  if (other._kind == Kind::A) {
    if (_kind != Kind::A) new (&_aVal) string; // *
    _aVal = other.aVal;
  } else if (other._kind == Kind::B) {
    _bVal = other.bVal;
  } else {
    _noVal = other.noVal;
  }
  _kind = other._kind;
  return *this;
}
总而言之,做这件事的正确方法是什么(以及为什么),或者这有什么关系



*之所以出现这一行,是因为我最初的实现直接设置了
aVal
,而没有确认字符串是否在那里初始化过,它就崩溃了。

您的第一个版本让我很紧张,因为在调用
This->~MyC()
之后,您从未真正调用过
This
上的构造函数,所以你的对象应该被认为是无效的。即使代码立即强制所有当前成员变量进入理论有效性,也很容易出现bug

你的第二个选择似乎更好,但仍有三个地方必须在每次有人加入工会时更新。。。假设您的
Kind
enum在 你只展示了三个。。。我想那迟早会发生的。同样,容易出现bug

我可以想出另外两个选择:

(一)

在使用析构函数“就地”后,就地使用现有的复制构造函数以避免泄漏。有点粗糙,更干净,无需额外维护

这导致您只需要在两个地方进行更改,而在字符串到字符串的情况下,这会降低一点效率。我发现在大多数情况下,可维护性胜过效率。哪些案例?询问你的档案员

警告:正如Chris在下面的评论中指出的,当处理从MyC派生的类时,这将非常失败,除非该类同样有自己的赋值运算符。如果在某个派生类上调用它,则该类将

  • 泄漏调用派生类的析构函数所需的任何内容
  • 将派生类转换为MyC的实例。大部分。我怀疑派生的析构函数仍然会被调用,有很多潜在的不好之处。
    • OTOH,这可以清理我们之前调用错误的析构函数时“泄漏”的所有东西。这要看情况而定
一方面,这段代码是一个在没有网的情况下走钢丝绳的极好例子。另一方面,直接使用new/delete也是如此。我们只是有更多的练习走那条特定的绳子,我们有像
unique\u ptr
shared\u ptr
这样的网络,我们可以(而且通常应该)使用

(二)

根据Richard的评论,用一些任意/变体模板(std::variant,boost::any,随便什么)包装您的工会。我的上一个雇主有3个不同的变体/任意类,由3个不同的程序员编写,其中至少有两个显然懒得先查看我们的代码库。您的公司可能也有自己的实现

不要以为他们的效率会降低。再次:询问你的档案员

无关:在前雇主的代码库中搜索诸如“请勿入住”、“亵渎”等有趣的短语,发现了各种有趣的事情。它是一家游戏公司,所以它的“保留力”远不如其他地方。它适合娱乐性阅读


使用现有的测试代码并在类中存储一个
std::variant
来代替
union
,怎么样?您可以保留额外的行为/需求,只需实现更少的代码。我可以,但我的印象是
std::variant
的开销更大,而且由于我的类非常简单,所以这不是什么大问题,除非有一些非常大、复杂的问题我真的不想自己解决(与
std::shared_ptr
)一样。我强烈建议您使用
std::variant
;它唯一的内存开销是单个
大小(我相信)对于类型索引,这并不多。像
std::variant
这样的低级类可能很难得到正确的结果,特别是如果您希望有适当的异常安全性。选项1一点也不干净。这是一个可怕的反模式,实际上永远不应该使用。1)如果构造函数抛出,您将留下一个死对象。2) 即使构造函数成功,也不能保证myC是正确的构造类型!考虑Myc是基类,这实际上指向派生类型。例如,这种方法将把一只
替换成一只
动物
,任何人仍然认为它是一只狗都会遇到严重的麻烦。这是一个很好的观点,尽管公平地说,破坏者并不是虚拟的,这让我相信这不会是一个问题。然后,可以通过确保所有子类都实现自己的赋值操作符来解决这个问题,而赋值操作符很容易出现另一种错误。我不确定RTTI能不能把你从一号洞挖出来。如果你能在原地的话也很有趣。
MyC& MyC::operator &(const MyC& other) {
  if (other._kind == Kind::A) {
    if (_kind != Kind::A) new (&_aVal) string; // *
    _aVal = other.aVal;
  } else if (other._kind == Kind::B) {
    _bVal = other.bVal;
  } else {
    _noVal = other.noVal;
  }
  _kind = other._kind;
  return *this;
}
MyC& operator =(const MyC& other)
{
    this->~MyC();
    new (this) MyC(other);
}