隐式定义与显式声明的构造函数

Implicitly defined vs Explicitly declared constructor

隐式定义和显式声明的 default/copy 构造函数有什么区别? 明确声明

    struct road{
     std::string id;
     std::string type;
     std::vector<int> nodes;
     road() = default;
     road(const & road c_road) = default;
     road(road && m_road);
   };

隐式定义

struct road{
 std::string id;
 std::string type;
 std::vector<int> nodes;
 road(road && m_road);
};

另外,与像

这样定义我自己的构造函数有什么区别
road::road(){}

road::road(const road & c_road):id(c_road.id)), type(c_road.type)),
  nodes(c_road.nodes){}

我的问题是,我是否需要显式声明一个默认构造函数(= default; version)或者我应该只依赖隐式构造函数?有任何版本更快或更安全吗?有没有版本"wrong"?

您可能想查看 "Rule of Zero"。您的 class 目前不需要构造函数,因为您的 class 将执行成员复制,您的成员已经为其定义了 copy/move 构造函数。如果您必须管理资源或处理更棘手的类型,情况就会不同。理想情况下,您的 class 将如下所示:

struct road{
  std::string id;
  std::string type;
  std::vector<int> nodes;
};

如果您用户声明复制构造函数但省略移动构造函数,编译器将不会为您生成移动构造函数。所以你需要这三个。同样,如果您决定实现赋值运算符,则需要对两者进行 copy assignment/move 赋值。这分别被称为 3/5 规则。保持简单,只定义您需要的内容。

显式默认(内联)与隐式默认:一般来说,这里的区别很小。这主要是一种风格选择。就个人而言,对于小 classes(< 1 个屏幕)我可能不会打扰,因为很容易看出它们没有在任何地方定义,并且它增加了 class 的长度.

对于更长、更重要的 classes,明确一点很好。这将立即告诉用户它是否具有值语义(至少是可移动的)或身份语义(不可移动的),它是否可复制,它是否可能是不可变的(在这种情况下它是不可分配的)等

一个真正的区别是,如果您确实声明了一些特殊成员,则不会隐式生成其他成员。最常见的例子是如果你必须自己实现复制 constructor/assignment。这将禁用移动的自动生成 constructor/assignment。所以你必须明确地默认它们(如果你想要默认值)。

许多人忽略的另一个重要区别:如果您的 class 分为 .h 和 .cpp 文件(大多数 non-template classes 应该是),两者这些方法导致在 header 中生成所有代码。这会减慢编译速度。所以第三种方法是这样做:

// .h file
struct Foo {
    Foo(const Foo&); // declare, but not define
};

// .cpp
Foo::Foo(const Foo&) = default;

这仍然利用生成的成员,但将代码保留在 .cpp 中您需要的位置。因此,对于大的 non-template classes,您通常只想 =delete 或声明事物,而不是 =default 或让它们隐式生成!

就定义您自己的特殊成员而言:如果默认值有效,您永远不应该自己定义特殊成员。并且您应该尝试使默认值起作用。当您自己定义一个构造函数时,您通常必须提及每个成员一次。如果您添加或删除成员,这将导致维护工作,包括一些可能被默默遗忘的工作。参见零规则:http://www.nirfriedman.com/2015/06/27/cpp-rule-of-zero/.

一个特例:默认构造函数。如果您有任何其他构造函数,则不会隐式生成它。然后,如果你想要它回来,=default 它似乎同样容易,并且简单地定义它。这些是不一样的。特别是,后者仍然算作 user-defined 构造函数,这意味着例如 class 永远不会被视为 trivially_constructible,因此不会是 trivial,并且因此不会是pod。这在某些应用程序中可能很重要。