Trying to understand c++ string a little more here. Compiler here is g++ 4.7.3.
Question 1: In the following code snippet, will the compiler be smart enough to free the user data (in the heap, implicitly pointed to by s) at the end of the function? I think the answer is yes, just want to confirm.
void dummy() {
string s;
s.append("hello");
//more append
}
Question 2: in the following snippet, compiler will not free the user data pointed to by s when the function returns. Is that right? If so, the user data may be freed in the caller by the compiler (if the caller function itself doesn't return the string object).
string dummy() {
string s;
s.append("hello");
//more append
return s;
}
//some caller
string s1 = dummy();
In question 1, yes the memory for the variable s will be freed when the function dummy ends because that was the enclosing scope of the s variable.
In question 2, the compiler will likely use return value optimization to avoid having to copy the memory from s into s1, since s is about to fall out of scope.
Question 1: In the following code snippet, will the compiler be smart enough to free the user data (in the heap, implicitly pointed to by s) at the end of the function?
Yes, it is required to. At the end of the function scope, the string's destructor will be called, which will free the memory allocated.
The object s is said to have "automatic storage duration", and it's described by the standard via §3.7.3/1:
Block-scope variables explicitly declared register or not explicitly declared static or extern have au- tomatic storage duration. The storage for these entities lasts until the block in which they are created exits.
Question 2: in the following snippet, compiler will not free the user data pointed to by s when the function returns. Is that right? If so, the user data may be freed in the caller by the compiler (if the caller function itself doesn't return the string object).
The compiler may elide the copy there, using what's called RVO (return value optimization). Specifically the standard words this in the following way, in §12.8/31:
This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
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
in a throw-expression (5.17), when the operand is the name of a non-volatile automatic object (other than a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost enclosing try-block (if there is one), the copy/move operation from the operand to the exception object (15.1) can be omitted by constructing the automatic object directly into the exception object
when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move
when the exception-declaration of an exception handler (Clause 15) declares an object of the same type (except for cv-qualification) as the exception object (15.1), the copy operation can be omitted by treating the exception-declaration as an alias for the exception object if the meaning of the program will be unchanged except for the execution of constructors and destructors for the object declared by the exception-declaration. [ Note: There cannot be a move from the exception object because it is always an lvalue. —endnote]
Related
In the following example I am expecting only a single copy-construction, as I thought the intermediate copies would by copy elided. The only required (I thought?) copy would be in the constructor of B to initialize the member variable a.
#include <iostream>
struct A
{
A() = default;
A(A const&) { std::cout << "copying \n"; }
};
struct B
{
B(A _a) : a(_a) {}
A a;
};
struct C : B
{
C(A _a) : B(_a) {}
};
int main()
{
A a{};
C c(a);
}
When I execute this code (with -O3) I see the following output
copying
copying
copying
Why aren't these intermediate copies elided?
Here are the cases, where copy elision is allowed (class.copy/31):
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
in a throw-expression, when the operand is the name of a non-volatile automatic object (other than
a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost
enclosing try-block (if there is one), the copy/move operation from the operand to the exception
object (15.1) can be omitted by constructing the automatic object directly into the exception object
when a temporary class object that has not been bound to a reference (12.2) would be copied/moved
to a class object with the same cv-unqualified type, the copy/move operation can be omitted by
constructing the temporary object directly into the target of the omitted copy/move
when the exception-declaration of an exception handler (Clause 15) declares an object of the same type
(except for cv-qualification) as the exception object (15.1), the copy/move operation can be omitted
None of these are true for your example (we are not in a return statement, throw-expression or exception-declaration. And there are no temporaries in your example at all.), so copy happens each time you expect to happen.
Note, that copy elision is allowed in the mentioned cases, but not mandatory. So even, for these cases, the compiler is allowed to emit copies (this is true for C++11. In C++17, there are some cases, where copy elision is mandatory. But, none of your example cases allows elision in C++17 either.)
Expanding on the discussion under the other answer, the copy here has side effects (printing to console), and so does not qualify for copy optimization.
Copy elision, however, is allowed regardless of side effects, per the cppreference article on copy elision. It's not allowed here because you have lvalues all the way. None of the copies you hoped to eliminate were of an rvalue or prvalue. To make them an rvalue, you need to cast using std::move or construct them as nameless temporaries as part of the constructor call.
Again, the cppreference article explains it much better.
You are passing the a object to the C constructor by value. And then it is being passed to the B constructor by value.
Looking at the following example. Does the C++ standard guarantee that the value of object.x will be equal to 1 at the end? What if I don't call the destructor object.~Class();?
#include <new>
class Class
{
public:
Class() {}
~Class() {}
int x;
};
int main()
{
Class object;
object.x = 1;
object.~Class();
new (&object) Class();
object.x == ?
Class object_2;
object_2.x = 1;
new (&object_2) Class();
object_2.x == ?
return 0;
}
No.
The object whose x is equal to 1 was destroyed.
You know that, because you're the one who destroyed it.
Your new x is uninitialised and has an unspecified value, which may be 1 in practice due to memory re-use. That's no different than for any other uninitialised value.
Update
Since there seems to be a lot of people throwing about assertions, here are some facts, direct from the standard.
There is no direct statement about this case because it follows from the general rules governing what objects are and what object lifetime means.
Generally, once you've grokked that C++ is an abstraction rather than a direct mapping of bytes, you can understand what's going on here, and why there is no such guarantee as that the OP seeks.
First, some background on object lifetime and destruction:
[C++14: 12.4/5]: A destructor is trivial if it is not user-provided and if:
the destructor is not virtual,
all of the direct base classes of its class have trivial destructors, and
for all of the non-static data members of its class that are of class type (or array thereof), each such class has a trivial destructor.
Otherwise, the destructor is non-trivial.
[C++14: 3.8]: [..] The lifetime of an object of type T ends when:
if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or
the storage which the object occupies is reused or released.
[C++14: 3.8/3]: The properties ascribed to objects throughout this International Standard apply for a given object only during its lifetime. [..]
[C++14: 3.8/4]: A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression (5.3.5) is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.
(Most of this passage is irrelevant for your first case, as it does have an explicit destructor call: your second violates the rules in this paragraph and thus has undefined behaviour; it shall not be discussed further.)
[C++14: 12.4/2]: A destructor is used to destroy objects of its class type. [..]
[C++14: 12.4/11]: [..] Destructors can also be invoked explicitly.
[C++14: 12.4/15]: Once a destructor is invoked for an object, the object no longer exists. [..]
Now, some specifics. What if we were to inspect object.x before the placement-new?
[C++14: 12.7/1]: [..] For an object with a non-trivial destructor, referring to any non-static member or base class of the object after the destructor finishes execution results in undefined behavior.
Wow, okay.
And what if we were to inspect it after the placement-new? i.e. what value does the x in the new object hold? Is it guaranteed, as the question asks, that it'll be 1? Bear in mind that Class's constructor includes no initialiser for x:
[C++14: 5.3.4/17]: A new-expression that creates an object of type T initializes that object as follows:
If the new-initializer is omitted, the object is default-initialized (8.5); if no initialization is performed, the object has indeterminate value.
Otherwise, the new-initializer is interpreted according to the initialization rules of 8.5 for direct-initialization.
[C++14: 8.5/16]: The initialization that occurs in the forms
T x(a);
T x{a};
as well as in new expressions (5.3.4), static_cast expressions (5.2.9), functional notation type conversions (5.2.3), and base and member initializers (12.6.2) is called direct-initialization.
[C++14: 8.5/17]: The semantics of initializers are as follows. The destination type is the type of the object or reference being initialized and the source type is the type of the initializer expression. If the initializer is not a single (possibly parenthesized) expression, the source type is not defined. [..]
If the initializer is (), the object is value-initialized.
[..]
[C++14: 8.5/8]: To value-initialize an object of type T means:
if T is a (possibly cv-qualified) class type (Clause 9) with either no default constructor (12.1) or a default constructor that is user-provided or deleted, then the object is default-initialized.
[..]
[C++14: 8.5/7]: To default-initialize an object of type T means:
if T is a (possibly cv-qualified) class type (Clause 9), the default constructor (12.1) for T is called (and the initialization is ill-formed if T has no default constructor or overload resolution (13.3) results in an
ambiguity or in a function that is deleted or inaccessible from the context of the initialization);
if T is an array type, each element is default-initialized;
— otherwise, no initialization is performed.
If a program calls for the default initialization of an object of a const-qualified type T, T shall be a class type with a user-provided default constructor.
[C++14: 12.6.2/8]: In a non-delegating constructor, if a given non-static data member or base class is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no
ctor-initializer) and the entity is not a virtual base class of an abstract class (10.4), then:
if the entity is a non-static data member that has a brace-or-equal-initializer, the entity is initialized as specified in 8.5;
otherwise, if the entity is an anonymous union or a variant member (9.5), no initialization is performed;
otherwise, the entity is default-initialized (8.5).
[C++14: 8.5/12]: If no initializer is specified for an object, the object is default-initialized; if no initialization is performed, an object with automatic or dynamic storage duration has indeterminate value.
So, does the standard guarantee that your replacement object's x has value 1? No. It does not.
In practice, why might it not be? Well, any number of reasons. Class's destructor is non-trivial so, per 3.8, the first object's lifetime has ended immediately after you called its destructor.
Technically, the compiler is then free to place an object at that location as long as it's destroyed by the time the placement-new takes effect. There's no reason for it to do so here, but there's nothing prohibiting it; more importantly, a simple { int x = 5; x = 42; } in between the destructor call and the placement-new would be more than entitled to re-use that memory. It's not being used to represent any object at that point!
More realistically, there are implementations (e.g. Microsoft Visual Studio) that, for debug-mode programs, write a recognisable bit-pattern into unused stack memory to aid in diagnosing program faults. There's no reason to think that such an implementation wouldn't hook into destructors in order to do that, and such an implementation would be overwriting your 1 value. There is nothing in the standard prohibiting this whatsoever.
Indeed, if I replace the ? lines in your code with std::cout statements, so that we actually inspect the values, I get a warning about an uninitialised variable and a value 0 in the case where you used a destructor:
main.cpp: In function 'int main()':
main.cpp:19:23: warning: 'object.Class::x' is used uninitialized in this function [-Wuninitialized]
std::cout << object.x << '\n';
^
0
1
Live demo
I'm unsure how much more proof you need that the standard does not guarantee a 1 value here.
inline void my_assert( bool cond, const std::exception &e = my_assert_failed() )
{
if ( !cond )
throw e;
}
The standard ensures that:
A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full expression containing the call.
And for a thrown temporary object:
The temporary persists as long as there is a handler being executed for that exception.
Can I infer that a temporary that is passed to my_assert survives until the catch block finishes?
From N4296 (first draft after final C++14) [15.1p3]:
Throwing an exception copy-initializes (8.5, 12.8) a temporary object,
called the exception object. The temporary is an lvalue and is used
to initialize the variable declared in the matching handler (15.3).
So you can't assume that your temporary "survives the throw". If throwing, the copy constructor of an exception object of type std::exception will be called with e as the argument. The temporary that e is bound to will be destroyed when control leaves the full expression containing the call to my_assert (either after a normal return or as part of stack unwinding, since you're conditionally throwing the exception).
There are circumstances when the copy construction of the exception object can be elided, but this is not one of them, according to [12.8p31.2]:
— in a throw-expression (5.17), when the operand is the name of a
non-volatile automatic object (other than a function or catch-clause
parameter) whose scope does not extend beyond the end of the innermost
enclosing try-block (if there is one), the copy/move operation from
the operand to the exception object (15.1) can be omitted by
constructing the automatic object directly into the exception object
(emphasis mine)
Consider that TEST code:
#include <iostream>
using namespace std;
class Klass
{
public:
Klass()
{
cout << "Klass()" << endl;
}
Klass(const Klass& right)
{
cout << "Klass(const Klass& right)" << endl;
}
};
Klass create(Klass a)
{
cout << "create(Klass a)" << endl;
return a;
}
int main()
{
const Klass result = create(Klass());
}
Compiling with:
g++ -O3 rvo.cpp -o rvo
The output is:
$ ./rvo
Klass()
create(Klass a)
Klass(const Klass& right)
I was expecting the compiler to use the RVO mechanism in order elide every COPY CTOR call, to avoid copying the return value AND the parameter of the function create(). Why isn't it the case?
The standard allows copy elision only in case where you pass a temporary as a function argument.
The two elisions you're expecting are bolded below:
[C++11: 12.8/31]: When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization. This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
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
in a throw-expression, when the operand is the name of a non-volatile automatic object (other than a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost enclosing try-block (if there is one), the copy/move operation from the operand to the exception object (15.1) can be omitted by constructing the automatic object directly into the exception object
when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move
when the exception-declaration of an exception handler (Clause 15) declares an object of the same type (except for cv-qualification) as the exception object (15.1), the copy/move operation can be omitted by treating the exception-declaration as an alias for the exception object if the meaning of the program will be unchanged except for the execution of constructors and destructors for the object declared by the exception-declaration. [..]
It didn't happen for the return value because the non-volatile name was a function parameter.
It has happened for the construction into create's parameter, otherwise you'd have seen:
Klass()
Klass(const Klass& right)
create(Klass a)
Klass(const Klass& right)
The copy you see is a copy for the "return" statement in the "create" function. It cannot be eliminated by RVO, as it is not possible to construct the return value directly. You requested to "return a". A copy is needed here; there is no way to return an object without it.
In a standard speak, following condition of [C++11: 12.8/31] is not met
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
As for the reasons, it is not an arbitrary rule, it makes sense from implementation point of view, as this is what is not possible to do with a function parameters:
constructing the automatic object directly into the function’s return value
You are copying the function parameter. You cannot elide this copy without inlining, as the parameter already exists before you enter the function, therefore you cannot construct that object into the return value directly instead.
This may sound naive. I want to know what happens when i explicitly call a constructor like this:
class A{
/*...*/
public:
A(){}
};
int main(){
A();
return 0;
}
Is a useless object created which remains in the memory until the scope of main() ends?
You create an object that lasts until the end of the statement.
Its considered a nameless temporary which gets destroyed after the end of the full expression. In this case, the point right after the semicolon. To prove this, create a destructor with a print statement.
when i explicitly call a constructor like this
You are not calling a constructor here; but creating a temporary object which gets destructed immediately. Constructor can be called explicitly with an object of that type (which is not advisable).
Is a useless object created which remains in the memory until the
scope of main() ends?
It doesn't have scope till the function ends, but till the ; ends.
Strictly speaking you can never make a direct call to a constructor in C++. A constructor is called by the implementation when you cause an object of class type to be instantiated.
The statement A(); is an expression statement and the expression is a degenerate form of an explicit type conversion (functional notation). A refers to the type, strictly speaking constructors don't have names.
From the standard (5.2.3 [expr.type.conv] / 2:
The expression T(), where T is a simple-type-specifier for a non-array complete object type or the (possibly cv-qualified) void type, creates an rvalue of the specified type, which is value-initialized [...].
Because your class type has a user-declared default constructor the value-initialization of this temporary will use this constructor. (see 8.5 [dcl.init]/5)
Okay, I re-visited temporary and found that in the above example, it's actually a part of expression that is initializing an object. So yes, the scope ends at ;
Here:
When a temporary object is created to initialize a reference variable, the name of the temporary object has the same scope as that of the reference variable. When a temporary object is created during the evaluation of a full-expression (an expression that is not a subexpression of another expression), it is destroyed as the last step in its evaluation that lexically contains the point where it was created.