为什么非易失性 T 从易失性 T 到 T 的转换应该是不平凡的?

Hom*_*ave 1 c++ constructor volatile

    struct T {
        int a;
        T() = default;
        T(const T &) = default;
        T(const volatile T &src) { a = src.a; } 
    };
Run Code Online (Sandbox Code Playgroud)

如果我们不提供采用 cvref 参数的复制构造函数,那么T就很简单,否则就不是这样。

按照 C++ 标准,看起来这是预期的。

我可以理解易失性 T并不简单,因为易失性类型可能需要不同的复制方法。

但是,我不太明白为什么具有用户提供的从易失性 TT转换的非易失性类型T 也应该被认为是不平凡的?

任何评论将不胜感激。

Dan*_*ury 5

我不同意 Ted Lyngmo 的回答,但我认为这可能提供更多背景信息:

如果满足以下条件,则类型是平凡的:

  1. 它有一个简单的默认构造函数;[*]
  2. 它具有的每个合格的复制构造函数、移动构造函数、复制赋值运算符或移动赋值运算符都是微不足道的,而且它至少具有其中之一;和
  3. 它的析构函数很简单。

您的类型很重要,因为它不满足条件 (2)。它有一个合格的复制构造函数,该复制构造函数是微不足道的,而另一个则不是。


对我来说,这只是将问题后退了一步(尽管对于真正的专家来说,以下所有内容可能都是显而易见的):为什么我们不能写

struct T {
    int a;
    T() = default;
    T(const T &) = default;
    T(const volatile T &src) = default;
};
Run Code Online (Sandbox Code Playgroud)

如果允许这样做,那么为 const 易失性引用生成的复制构造函数可能会与您在此处编写的内容相匹配,然后T满足普通类型的定义。

这样做的问题在于,这意味着每当您编写平面结构时,它都会自动配备一个采用易失性实例的复制构造函数,这可能很危险。假设我们有一个像这样的结构

struct X
{
  char data[256];
  int num_reads;
  int num_writes;
};
Run Code Online (Sandbox Code Playgroud)

我们有一个volatile X实例映射到内存,每次我们读取一些实例data[i]都会导致num_reads增量。当我们使用 时,这是完全合法的情况volatile,并且我们显然不希望编译器生成默认的复制构造函数。


另一方面,我想我们可能会问为什么采用 const 易失性引用的构造函数甚至被认为是复制构造函数。如果您确实提供了这样的构造函数,则它可以用于复制非易失性实例(假设您没有为该情况提供另一个构造函数),这可能是支持将其称为复制构造函数的一点。另一方面,我想写这样的东西并不难

S(const S& s) : S(static_cast<const volatile S&>(s)) { }
Run Code Online (Sandbox Code Playgroud)

转发给它。(但是我很有可能错过了一些很好的理由,我们需要将其视为复制构造函数才能使某些东西正常工作——也许是为了让泛型集合类型采用易失性类型参数?)

也就是说,如果这给您带来了问题,并且您不需要构造函数采用 const 易失性引用来计数作为复制构造函数,则可以很容易地编写以下内容:

struct T {
    int a;
    T() = default;
    T(const T &) = default;
    T(const volatile T &src, bool) { a = src.a; }
};
Run Code Online (Sandbox Code Playgroud)

不幸的是,您无法更进一步并为该虚拟参数提供默认参数,因为这使其再次成为复制构造函数!但这里有一个版本,可以让你摆脱更多的麻烦:

struct T {
  int a;
  T() = default;
  T(const T &) = default;

  template<int = 0> T(const volatile T &src) { a = src.a; }
};
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为模板构造函数不算作复制构造函数。我不确定那里的确切原理,但我的假设是,一般来说,它可能相当于确定模板构造函数是否具有某些参数集,使其具有与复制构造函数相同的签名的停止问题。假设情况确实如此,那么确定一个类型是否可简单复制通常是不可计算的,这会削弱这个概念的实用性。


[*] 从技术上讲,条件实际上是每个符合条件的默认构造函数都是微不足道的,而且至少有一个。有什么不同?一种类型可以有多个默认构造函数:

struct S
{
  S() = default;
  S(int x = 0) { }
};
Run Code Online (Sandbox Code Playgroud)