Konstantinoscs
Konstantinoscs

Reputation: 23

Implicitly defined vs Explicitly declared constructor

What is the difference of an implicitly defined and explicitly declared default/copy constructors? Explicitly declared

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

Implicitly defined

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

also what is the difference from defining my own constructors like

road::road(){}

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

My question is, do I need to explicitly declare a default contructor (= default; version) or should I just rely on the implicit one? Is any version faster or safer in any way? Is any version "wrong"?

Upvotes: 2

Views: 344

Answers (2)

Nir Friedman
Nir Friedman

Reputation: 17704

Explicitly defaulted (inline) vs implicitly defaulted: there's very little difference here, in general. It's mostly a stylistic choice. Personally, for small classes (< 1 screen) I probably wouldn't bother, since it is easy enough to see they aren't defined anywhere, and it adds a bunch of length to the class.

For longer, more important classes, it's nice to be explicit. This will immediately tell the user whether it has value semantics (at least movable) or identity semantics (immovable), whether it is copyable, whether it may be immutable (in which case it is unassignable), etc.

One real difference is that if you do declare some of the special members, others will not be implicitly generated. The most common example is if you have to implement the copy constructor/assignment yourself. This will disable automatic generation of the move constructor/assignment. So you would have to explicitly default them (if you want the defaults).

Another important difference that many people gloss over: if your class is divided into .h and .cpp files (as most non-template classes should be), both of these approaches result in all of the code being generated in the header. This slows compilation. So a third approach is to do this:

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

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

This still takes advantage of generated members, but keeps code in the .cpp where you want it. So, for big, non-template classes, you only typically want to =delete, or declare things, but not =default or let them be implicitly generated!

As far as defining your own special members goes: you should never define the special members yourself, if the defaults work. And you should try to make the defaults work. When you define a constructor youself, you have to typically mention each member once. If you add or remove members, this will result in maintenace work, including some that can be silently forgotten. See the Rule of Zero: http://www.nirfriedman.com/2015/06/27/cpp-rule-of-zero/.

One special case: the default constructor. It will not be implicitly generated if you have any other constructors. Then, if you want it back, it seems equally easy to =default it, and simply defining it trivially. These are not the same. In particular, the latter still counts as a user-defined constructor, and it means for example that the class will never be considered trivially_constructible, and therefore will not be trivial, and therefore will not be pod. This can be important in some applications.

Upvotes: 0

OwO
OwO

Reputation: 361

You might want to look into the "Rule of Zero". Your class as it stands needs no constructors because your class will perform a memberwise copy for which your members already have copy/move constructors defined. It would be a different story if you had to manage a resource or deal with trickier types. Ideally, your class will look like this:

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

If you user-declare the copy constructor but omit the move constructor, the compiler will not generate the move constructor for you. So you need all three. Similarly if you decide to implement the assignment operators, you need both copy assignment/move assignment. This is known as the Rule of 3/5 respectively. Keep it simple and only define what you need.

Upvotes: 2

Related Questions