I studied that during initialization of object, for example
string s = "Hello world";
If RHS is implicitly convertible to LHS type object, then Copy Constructor would be called. But I have a friend who is pretty sure that constructor which takes char pointer as an argument would be called.But I told him that constructor with charpointer would be called only in cases as below
string s("Hello world");
Is that correct?
Doing
string s = "Hello world";
is equivalent to
string s( string( "Hello world" ) );
so both the constructor taking char const* and the copy-constructor are called. However, the standard allows copy-elision where the copy-constructor call is elided (not done).
Yes and no. Both are called.
string s = "Hello world";
This is copy initialization. It calls the conversion constructor and constructs a temporary string from "Hellow world" and then uses that temporary with the copy constructor to construct s. (subject to optimizations)
string s("Hello world");
Is direct initialization and calls the conversion constructor directly, constructing s from "Hello world".
Related
In the class defined as below named foo
class foo{
private:
string str;
public:
foo operator = (string s){
str = s;
}
};
int main(){
foo a = "this initialization throwing an error";
foo b;
b = "but assignment after declaration is working fine";
}
error: conversion from 'const char [38]' to non-scalar type 'foo' requested
The above error is caused only when I am assigning value to the object instance with declaration but if I am assigning separately from declaration then the overloaded equal = operator is working fine.
I want in any method to assign an string to the object with equal operator
and as a declaration like foo a = "abcd";
When you have
type name = something;
You are not doing assignment but instead you have copy-initialization (do not that there can be a move even though it is called copy). This means that you need a constructor in your class whose parameter matches that of the thing on the right hand side of the =.
In this case you do not have a constructor that takes a std::string, const char* or const char[] so the compiler is unable to construct an instance there.
The reason the second case works is that
b = "but assignment after declaration is working fine";
Is not trying to construct anything and therefore it calls the assignment operator which does work because
"but assignment after declaration is working fine"
can be converted to a std::string.
If you want your class to be constructable from a string literal or cstring then you can add a constructor to it in the form of
foo(const char* str) : str(str) {}
What you have in the first case is called copy initialization and as stated in documentation:
The equals sign, =, in copy-initialization of a named variable is not
related to the assignment operator. Assignment operator overloads have
no effect on copy-initialization.
Hense the error. So possible solutions:
First you can create a constructor, that accepts std::string:
foo( const std::string &s );
that will allow you to create foo by this:
foo f( "abc" );
or even this:
foo f = foo( "abc" );
but your statement will still fail, because of:
in addition, the implicit conversion in copy-initialization must
produce T directly from the initializer, while, e.g.
direct-initialization expects an implicit conversion from the
initializer to an argument of T's constructor.
so to make your statement work as it is you need to add this ctor:
foo( const char *str );
Note: when you add proper ctor you do not need to define assignment operator - conversion constructor will be used. And your overloaded assignment operator should return reference to foo.
When you create an object, the code initializes it with a constructor, even when the creation uses an = sign:
struct S {
S(int);
};
S s = 3; // initialization, not assignment
Formally, that initialization uses S(int) to create a temporary object of type S, and then uses the copy constructor to construct the object s from the temporary object.
That's vastly different from assignment, which deals with an already existing object:
S s1;
s1 = 3; // assignment
Here, the assignment would use the assignment operator if S defined one. That's why the b = line in the original code works.
Your problem is that
foo a = "initialization string";
attempts to create an object of type foo however there is no constructor defined that accepts a parameter of type string.
You could define one like this:
foo(const std::string& s) : str(s) {}
foo(const char* s) : str(s) {}
For starters though the compiler does not issue a diagnostic message nevertheless the assignment operator in the class definition
class foo{
private:
string str;
public:
foo operator = (string s){
str = s;
}
};
is invalid because it returns nothing while it has return type foo.
You should it write like
foo & operator = ( const std::string &s){
str = s;
return *this;
}
As you declared neither the default constructor nor the copy constructor then the compiler implicitly declared them instead of you.
So in fact your class has only two constructor. The default constructor that has the following declaration
foo();
and the copy constructor that has the following declaration
for( const foo & );
In this statement
foo a = "this initialization throwing an error";
the compiler assumes that in the right side there is an object of type foo and you are going to use it to create the object a. That is in this declaration the compiler tries to apply the implicitly created copy constructor. To so so it need to convert the string literal to an object of type foo. However the class does not have a conversion constructor that is a constructor with a parameter that can accept the string literal. As result the compiler issues the error
error: conversion from 'const char [38]' to non-scalar type 'foo'
requested
const char[33] is the type of the string literal in the right side of the declaration.
In this code snippet
foo b;
b = "but assignment after declaration is working fine";
at first there is created the object b of the type foo using the implicitly defined by the compiler default constructor and then in the second statement there is used the assignment operator that is explicitly defined in the class. As it follows from the definition of the operator it assigns data member str with a temporary object of type std::string that is constructed from the used string literal. It is possible because the class std::string has a corresponding conversion constructor.
The following quote from my C++ book:
When we use direct initialization, we are asking the compiler to use ordinary function matching to select the constructor that best matches the arguments
we provide. When we use copy initialization, we are asking the compiler to copy
the right-hand operand into the object being created, converting that operand if
necessary.
For me this bolded bit produces a bit of ambiguity. It makes it sound like the right-hand operand is converted to the class-type and then the copy-constructor is used, for instance;
string s = "hello";
would become...
string s = string("hello");
which uses the copy constructor. If this is true then my test program;
#include <iostream>
using namespace std;
class A{
public:
A(const A& b): data(b.data) { cout << "The first way" << endl;}
A(const char* c): data(c) { cout << "The second way" << endl;}
string data;
};
int main(){
A first("hello");
A second = "sup";
}
Should be producing "The second way, The second way, The first way". However it instead prints "The second way, The second way". From this I would've concluded it is using the const char* constructor and not the copy-constructor. I would be fine with this except later it says...
During copy initialization, the compiler is permitted (but not obligated) to skip the copy/move constructor and create the object directly. That is, the compiler is permitted to rewrite
string null_book = "9-999-99999-9";
into
string null_book("9-999-99999-9");
However, even if the compiler omits the call to the copy/move constructor, the
copy/move constructor must exist and must be accessible (e.g., not private) at that point in the program.
I'm not sure why the copy constructor even needs a mention in these examples, doesn't
string null_book = "9-999-99999-9"
always implicitly mean that the const char* constructor is being used anyway? Indeed it makes less sense to me that the copy-constructor needs to be defined in order for the above to work. But alas, if I put the "const A&" constructor as private (the rest public) then my program will not run. Why must the copy-constructor be defined for implicit conversions that don't even involve it? What constructor does "string null_book = "9-999-99999-9"" use?
string null_book = "9-999-99999-9"; means string null_book = string("9-999-99999-9");.
This uses the const char * constructor to construct a temporary object, and then null_book is copy/move constructed from the temporary object, and then the temporary object is destroyed.
(Copy/move construction means that a move constructor is used if available; otherwise a copy constructor).
However this scenario is also eligible for copy elision. You actually quoted the copy elision specification in your question so I won't repeat it.
The compiler may choose to use the same memory space for both null_book and the temporary object, and omit the calls to the temporary object destructor and the null_book copy/move constructor.
In your case the compiler did indeed choose to do that, which is why you do not see any copy constructor output.
Some compilers allow copy elision to be disabled by a switch, e.g. gcc/clang -fno-elide-constructors.
More info about copy elision
The following code, compiled on VS2013, never invokes std::string's move constructor (checked via setting breakpoints, the const ref copy constructor is invoked instead.
#include <iostream>
#include <string>
#include <stdlib.h> /* srand, rand */
#include <time.h> /* time */
struct foo
{
foo(std::string& str1, std::string& str2) : _str1(str1), _str2(str2) {}
~foo() { std::cout << "Either \"" << _str1 << "\" or \"" << _str2 << "\" was returned." << std::endl; }
std::string& _str1;
std::string& _str2;
};
std::string foobar()
{
std::string str1("Hello, World!");
std::string str2("Goodbye, cruel World.");
foo f(str1, str2);
srand(time(NULL));
return (rand() % 2) ? str1 : str2;
}
int main()
{
std::cout << "\"" << foobar() << "\" was actually returned." << std::endl;
return EXIT_SUCCESS;
}
I would expect the return statement in foobar() to invoke the move constructor since I'm returning a local (the rand() is to prevent NRVO), like stated as answers to questions such as Returning std::move of a local variable
The context of this is that I'm trying to add another example for my other question here: https://softwareengineering.stackexchange.com/questions/258238/move-semantics-in-c-move-return-of-local-variables
C++11 has a special case to allow for copy/move ellision when it's a local variable and is used as the return expression from a function:
C++11 12.8/31 "Copying and moving class objects":
in a return statement in a function with a class return type, when the
expression is the name of a non-volatile automatic object (other than
a function or catch-clause parameter) with the same cv-unqualified
type as the function return type, the copy/move operation can be
omitted by constructing the automatic object directly into the
function’s return value
But this case for copy elision is not met because the return statement you have is not simply "the name of a non-volatile automatic object".
Later, the standard mentions that
C++11 12.8/32 "Copying and moving class objects":
When the criteria for elision of a copy operation are met or would be
met save for the fact that the source object is a function parameter,
and the object to be copied is designated by an lvalue, overload
resolution to select the constructor for the copy is first performed
as if the object were designated by an rvalue. If overload resolution
fails, or if the type of the first parameter of the selected
constructor is not an rvalue reference to the object’s type (possibly
cv-qualified), overload resolution is performed again, considering the
object as an lvalue. [ Note: This two-stage overload resolution must
be performed regardless of whether copy elision will occur. It
determines the constructor to be called if elision is not performed,
and the selected constructor must be accessible even if the call is
elided. — end note ]
This allows the move operation to be used even when the return specifies an lvalue. However, this special case only applies under the conditions of the first sentence, which are not met in the case of of your example return statement.
You can force the issue:
return (rand() % 2) ? std::move(str1) : std::move(str2);
I read the C++ Primer and it is said when we use a '=' operator, or when a function parameter is a value of a class type or a function return type is a value of a class type, c++ will use what's called "copy initialization", which different with "direct initialization" that find the corresponding constructor based on parameter, "copy initialization" will use the "copy constructor". So in the following code, when I supplies "hello" as the argument to Foo, it should use the copy constructor, which is string (const string& str) to initialize the parameter. But my argument is "hello", which is a const char *, so what actually happened for the const char * to become a valid argument for the const string & parameter? Could some one give more detailed information? Thanks!
void Foo(string str){}
int main() {
Foo("hello");
}
The initialization of str in the above code has the same semantics as the following:
string str = "hello";
This is indeed what is called copy initialization. Assuming that string is std::string, there exists a constructor taking a single argument of type const char*, that is, string::string(const char*). This constructor will be invoked for the initialization. Calling this constructor creates a temporary of type string. Then the copy or move constructor of string is called to initialize str from the temporary. Typically, the extra copy or move is elided, and neither the copy nor move constructor is called at all, despite the name "copy initialization".
I've got a class ByteArray defined like this:
class ByteArray
{
public:
explicit ByteArray( unsigned int uiSize = 0 );
explicit ByteArray( const char * ucSource );
ByteArray( const ByteArray & other );
ByteArray & operator=( const char * ucSource );
ByteArray & operator=( const ByteArray & other );
}
While almost everything works, constructing a ByteArray by assignment doesn't compile.
ByteArray ba1( 5 ); // works
ByteArray ba2( ba1 ); // works
ByteArray ba3( "ABC" ); // works
ByteArray ba4; // works
ba4 = "ABC"; // works
ByteArray ba5 = "ABC"; // <<<----- doesn't compile!
The compiler gives me a Cannot convert 'const char *' to 'ByteArray'.
However, the "assignment-constructor" should be the same as the copy-constuktor, ie. the ba5 line should compile just as the ba3 line--- in contrast to the construction of ba4 and subsequent assignment. So, I'm not quite sure what problem the compiler is having.
I know that a solution would be to remove the explicit in front of the 3rd ctor. I would rather understand what's going on first, though...
Edit:
The answer states it nicely: ByteArray ba5 = "ABC"; would get compiled as ByteArray ba5( ByteArray("ABC") ); --- NOT as ByteArray ba5("ABC"); as I thought it would. Obvious, but sometimes you need someone to point it out. Thanks everyone for your answers!
Why use 'explicit' anyway? Because there is an ambiguity between unsigned int and const char *. If I call ByteArray ba( 0 ); both ctors would be able to handle that, so I need to forbid the implicit conversion and make it explicit.
ByteArray ba5 = "ABC"; is copy initialization, not assignment.
Think of it as
ByteArray ba5(ByteArray("ABC"));
or at least that's what the compiler sees. It's illegal in your case because of the explicit property of the constructor - the compiler wants to use that conversion constructor to perform copy initialization, but it can't because you didn't explicitly use it.
If you don't use explicit keyword, compiler is allowed to convert initialization using = (copy initialization) into initialization using constructor. But sometimes you don't want this behavior and thus you use explicit keyword to avoid this conversion. So you are actually getting the intended result.
Some examples that can be restricted with explicit constructor are
explicit T(const other_type &other);
T object = other;
f(other); // if f recieves object by value
return other; // if function returns object by value
catch ( T other);
T array [ N ] = { other };
C++11 12.3.1/2 "Conversion by constructor" says:
An explicit constructor constructs objects just like non-explicit constructors, but does so only where the direct-initialization syntax (8.5) or where casts (5.2.9, 5.4) are explicitly used.
The form:
ByteArray ba5 = "ABC";
Is copy-initialization (per 8.5/14), not direct-initialization (ByteArray ba3( "ABC" )) (per 8.5/15), so an explicit constructor cannot be used.
C++ has two types of initialization, copy initialization and
direct initialization. In the case of copy initialization, the
formal rules say that the copy constructor should be used; if
the expression doesn't have the correct type, it will be
converted. (The compiler is allowed to elide the extra copy,
but it still must ensure that the code is legal without the
eliding.) The initialization signaled by an = sign (which in
this case is not the assignment operator) uses copy
initialization, as does passing a parameter or returning
a value, or throwing or catching an exception. The
initialization which is marked by parentheses or braces (in
C++11) is direct initialization, as is the initialization in
a new expression, base and member initialization, and the
various explicit conversions (static_cast, etc.).
For actual assignment, of course, the rules are those of
a function call (which is what it is—no new variable is
constructed). In your case, ba4 = "ABC"; works because you
have an assignment operator which takes a char const*; no
implicit conversion is necessary.
(And while I'm at it: I'd avoid explicit on a copy
constructor. I'm not sure exactly what it means, and I'm not
sure that anyone else is either—the purpose of explicit
is to prevent the constructor from being used in implicit
conversions, and of course, the copy constructor can't be used
in any conversions anyway.)
The answers provided so far give insight into why the code doesn't work. But that doesn't explain why it shouldn't work.
Copy initialization is a way of initialising variables. Explicit is a way of ensuring that implicit conversions don't happen in construction or assignment. In my experience copy initialization always behaves the same way as direct initialization (though I don't know if this is guarenteed).
Why should the presence of "explicit" stop copy initialization being valid? Or, looking at it the other way, how can I get copy initialization to work with explict constructors? If not possible then surely this is a defect in the language specification - there doesn't seem to be any disadvantage to enabling it, providing the relevant functions are declared and defined.