Usually I see the = default
syntax used in the header. My understanding is that this is the same as if the functions are explicitly implemented in the header, see Foo
below.
#pragma once
class Foo
{
public:
Foo() = default;
Foo(const Foo& other) = default;
};
Purely out of curiosity, can the = default
be used in the source files as follows?
#pragma once
class Bar
{
public:
Bar();
Bar(const Bar& other);
};
#include "Bar.h"
Bar::Bar() = default;
Bar::Bar(const Bar&) = default;
As far as I know this is equivalent to explicitly implementing the functions in the cpp file.
The above Bar
example compiles with gcc-5.1
but does the standard allow for this usage?
As an aside, are there any benefits to using = default
in the source file versus the header?
Interfaces can have default methods with implementation in Java 8 on later. Interfaces can have static methods as well, similar to static methods in classes. Default methods were introduced to provide backward compatibility for old interfaces so that they can have new methods without affecting existing code.
The default methods are introduced in an interface since Java8. Unlike other abstract methods these are the methods can have a default implementation. If you have default method in an interface, it is not mandatory to override (provide body) it in the classes that are already implementing this interface.
Default implementations help with that. An interface member can now be specified with a code body, and if an implementing class or struct does not provide an implementation of that member, no error occurs. Instead, the default implementation is used.
Default methods enable you to add new functionality to existing interfaces and ensure binary compatibility with code written for older versions of those interfaces. In particular, default methods enable you to add methods that accept lambda expressions as parameters to existing interfaces.
Yes this is legal. From [dcl.fct.def.default]
Explicitly-defaulted functions and implicitly-declared functions are collectively called defaulted functions, and the implementation shall provide implicit definitions for them (12.1 12.4, 12.8), which might mean defining them as deleted. A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed.
Emphasis mine
And then they go on to detail you exact scenario with
struct nontrivial1 { nontrivial1(); }; nontrivial1::nontrivial1() = default; // not first declaration
So as long as the function is not implicitly marked as deleted then the function will be defined where you explicitly default it.
As an aside, are there any benefits to using
= default
in the source file versus the header?
The only "advantage" I can see it it allows existing code bases to change their cpp files to use modern techniques without having to change the header file. There is even a note in the standard:
Note: Declaring a function as defaulted after its first declaration can provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base.
One potential usage of defaulting in the source file instead of the header is to use the pimpl idiom with unique_ptr
. It requires a complete type for construction and destruction, so you can't define those special members in the header. You'd have to do:
Foo.h
struct Foo {
struct Impl;
unique_ptr<Impl> p;
Foo();
~Foo();
};
Foo.cpp
// Foo::Impl definition here
// now Impl isn't incomplete
Foo::Foo() = default;
Foo::~Foo() = default;
Yes, special member functions may be defaulted "out of line"; the compiler will generate the correct code and it will work as expected.
In fact, there is a rule relating to special members not being defaulted on first declaration, they are then considered user provided (and hence non-trivial).
A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed.
Link here [dcl.fct.def.default]. With the following example detailing your situation;
struct nontrivial1 { nontrivial1(); }; nontrivial1::nontrivial1() = default; // not first declaration
Its usefulness lies in what it does, it provides the default implementation, but not at the point of declaration, thus making it user-provided. As noted, this is useful when dealing with an as-yet incomplete type, such as when using the pimpl idiom. It could also be used to mark your type as non-trivial, thus prohibiting its use in code that requires a trivial type (e.g. std::is_trivial
).
There is a small change in the behaviour. Other TU than Bar.cpp
can't see that they are defaulted because they only see the header. So putting default in the cpp will make your class not trivially assignable and not trivially constructible.
There are some case that you want to do this: if your class hold a unique_ptr
to an incomplete type, it's a good practice to default the destructor in the cpp, because if you don't, classes that uses yours will be required the incomplete type's destructor to be visible.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With