Consider this example for initializer-list-constructor usage:
std::vector<std::string> v = { "xyzzy", "plugh", "abracadabra" };
std::vector<std::string> v({ "xyzzy", "plugh", "abracadabra" });
std::vector<std::string> v{ "xyzzy", "plugh", "abracadabra" };
Are there any differences (even slightly) between them?
In a large project, where you have to define a standard, which style would you choose?
I would prefer the first style, the third can be easly confused with a call to a constructor with args. Also the first style looks familiar to other programming languages.
There are two types of constructors in Java: Default constructor (no-arg constructor) Parameterized constructor.
An initialization list can be used to explicitly call a constructor that takes arguments for a data member that is an object of another class (see the employee constructor example above). In a derived class constructor, an initialization list can be used to explicitly call a base class constructor that takes arguments.
Initializer List is used in initializing the data members of a class. The list of members to be initialized is indicated with constructor as a comma-separated list followed by a colon. Following is an example that uses the initializer list to initialize x and y of Point class.
The initializer list is used to directly initialize data members of a class. An initializer list starts after the constructor name and its parameters.
In case of a vector
of string
s, there's no difference between the three forms. There can, however, be a difference between the first and the other two if the constructor taking the initializer_list
is explicit
. In that case, the first, which is copy-list-initialization, is not allowed, while the other two, which are direct-list-initialization, are allowed.
Because of that reason, my preference would be the third form. I'd avoid the second because the parentheses are redundant.
Further differences arise, as Yakk points out in the comments, when the type being constructed does not have a constructor taking an initializer_list
.
Say for instance, the type being constructed has a constructor that takes 3 arguments, all of type char const *
, instead of the initializer_list
constructor. In that case, forms 1 & 3 are valid, but 2 is ill-formed, because the braced-init-list cannot match the 3 argument constructor when enclosed in parentheses.
If the type does have an initializer list constructor, but the elements of the braced-init-list are not implicitly convertible to initializer_list<T>
, then other constructors will be considered. Assuming another constructor that is a match exists, form 2 results in an intermediate copy being constructed, while the other two don't. This can be demonstrated by the following example, compiled with -fno-elide-constructors
.
struct foo
{
foo(int, int) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
foo(foo const&) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
foo(std::initializer_list<std::string>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
};
int main()
{
foo f1 = {1,2};
std::cout << "----\n";
foo f2({1,2});
std::cout << "----\n";
foo f3{1,2};
}
Output:
foo::foo(int, int)
----
foo::foo(int, int)
foo::foo(const foo&)
----
foo::foo(int, int)
The following case is not part of the question, but still good to be aware of. Using nested braces can result in unintuitive behavior in certain cases. Consider
std::vector<std::string> v1{{ "xyzzy", "plugh", "abracadabra" }};
std::vector<std::string> v2{{ "xyzzy", "plugh"}};
v1
works as expected and will be a vector
containing 3 strings, while v2
results in undefined behavior. Refer to this answer for a detailed explanation.
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