“explicit”关键字如何影响 C++ 复制构造函数和函数参数?

How does the 'explicit' keyword affect C++ copy constructors and function parameters?

提问人:Andrés 提问时间:5/24/2023 最后编辑:user12002570Andrés 更新时间:5/25/2023 访问量:102

问:

修改复制构造函数的“explicit”关键字可能会导致问题。 作为函数参数传递的对象特别容易受到这些问题的影响。

这是我的代码:

#include <iostream>
#include <string>

class Pig{
public:
    std::string _name;
public:
    Pig(std::string n) : _name(n) {}
    //~Pig() = default;
    explicit Pig(const Pig &other) {
        std::cout << "copy ctor!" << std::endl;
        this->_name = other._name;
    }
};

void show(Pig p) {
    std::cout << p._name << std::endl;
}

int main() {
    Pig pig{std::string("hello")};
    show(Pig{pig});     // no pass
    // show(Pig(pig));  // no pass
    return 0;
}

编译器版本:g++ (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0。

上面提到的代码不能用 c++14 或更低版本编译, 但在 C++17 及更高版本中成功编译。

以下是编译器的错误:

test.cpp: In function ‘int main()’:
test.cpp:22:7: error: cannot convert ‘Pig’ to ‘std::string’ {aka ‘std::__cxx11::basic_string<char>’}
   22 |  show(Pig{pig});     // 不通过
      |       ^~~~~~~~
      |       |
      |       Pig
  • 我知道如何使用显式,我想显式调用复制构造函数以使其工作。

提前致谢!

我尝试使用 c++14 和 c++17 进行编译。

C++ 编译器错误 显式 复制省略省略

评论

1赞 Marek R 5/24/2023
这是一个复制品:godbolt.org/z/7Gvr49GPn
1赞 fabian 5/24/2023
@Dharmesh946好吧,我可以反驳这个:godbolt.org/z/vnTjaz7bE ;这与复制省略有关。恕我直言,无论如何,您都应该用作函数的签名......void show(Pig const& p)
0赞 Oersted 5/24/2023
@fabian确实,代码在 C++14 时失败了。可以通过将右值引用传递到 show 中来修复它。我不知道按值传递rvalue的规则有什么区别......Pig
2赞 fabian 5/24/2023
@Dharmesh946 区别在于,在一种情况下,您将传递对表达式创建的临时对象的引用,而在另一种情况下,您需要从该临时对象复制构造函数参数值;对于 >=C++17,由于复制省略,将删除此副本。Pig{pig}
1赞 Eljay 5/24/2023
“explicit”关键字如何影响 C++ 复制构造函数和函数参数?编译器不能再对式副本使用显式复制构造函数,这将导致大量问题并且没有任何好处。

答:

5赞 user12002570 5/24/2023 #1

先前的 C++17

问题是,在 C++17 之前,当将参数作为参数传递给函数时,将有参数的概念副本。也就是说,函数命名的参数是从传递的参数复制初始化的,并且由于复制 ctor 被标记,因此这为您提供了上述错误。Pig{pig}showpshowPig{pig}explicit

这可以从副本初始化中看出:

在以下情况下执行复制初始化

  • 按值将参数传递给函数时

(强调我的)


C++17

OTOH 从 C++17 开始,prvalue 直接构造到 的存储中。也就是说,没有来自 C++17 的参数的概念副本,因此副本 ctor 不需要是非显式的等。Pig{pig}p

从 C++17 开始,我们有强制性的复制省略

在以下情况下,编译器需要省略类对象的复制和移动构造,即使复制/移动构造函数和析构函数具有可观察到的副作用。这些对象被直接构造到存储中,否则它们将被复制/移动到该存储中。复制/移动构造函数不需要存在或可访问

  • 在对象的初始化中,当初始值设定项表达式是与变量类型相同类类型(忽略 cv-qualification)的 prvalue

(强调我的)

请注意,这是一个 prvalue,因此上述内容适用,复制构造函数不需要存在或可访问(并且可以是显式的)。Pig{pig}