[interprocess] default constructors

Hi, Is there any reason why named_mutex and managed_shared_memory don't have a default constructor?

Jan Stetka wrote:
Hi,
Is there any reason why named_mutex and managed_shared_memory don't have a default constructor?
Because in the review the two step construction (default ctor + open()) was considered harmful. I guess now it would be useful in the presence of move semantics. Is that your intended use case? Regards, Ion

Ion Gaztañaga wrote:
Jan Stetka wrote:
Hi,
Is there any reason why named_mutex and managed_shared_memory don't have a default constructor?
Because in the review the two step construction (default ctor + open()) was considered harmful. I guess now it would be useful in the presence of move semantics. Is that your intended use case?
Regards,
Ion _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
I'm don't known what "move semantics" are, my motive is so I can have named_mutex and managed_shared_memory in the global scope without having to instantiate as pointers.

Jan Stetka wrote:
I'm don't known what "move semantics" are, my motive is so I can have named_mutex and managed_shared_memory in the global scope without having to instantiate as pointers.
In other words, you want to create a default-constructed type and then swap it with a named constructed one when you have enough data to build a named object? I will add default-constructors to my to-do list. Regards, Ion

Ion Gaztañaga wrote:
Jan Stetka wrote:
I'm don't known what "move semantics" are, my motive is so I can have named_mutex and managed_shared_memory in the global scope without having to instantiate as pointers.
In other words, you want to create a default-constructed type and then swap it with a named constructed one when you have enough data to build a named object?
I will add default-constructors to my to-do list.
Regards,
Ion _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Does the shared memory get destroyed when theses types go out of scope or am I safe with local instances, if so I don't need the default constructors. You've designed the library so well that it is showing problems in my coding style :-)

Jan Stetka wrote:
Does the shared memory get destroyed when theses types go out of scope or am I safe with local instances, if so I don't need the default constructors. You've designed the library so well that it is showing problems in my coding style :-)
Sorry, I don't understand very well this point. Shared memory is never destroyed until explicitly removed, but it must be reopened if the shared_memory_object object is destroyed: //Imagine a default constructor is available: managed_shared_memory global_shm; void func(bool false_is_open_true_is_create) { if(false_is_open_true_is_create){ managed_shared_memory shm(create_only, "name", MemSize); global_shm.swap(shm); } else{ managed_shared_memory shm(open_only, "name", MemSize); global_shm.swap(shm); } //global_shm is usable here because it has been swapped //with a correctly opened instance global_shm.create<>(...); } If global_shm is destroyed. Shared memory is still in the system, although not mapped. To destroy it, you must explicily call shared_memory_object::remove("name"); If global_shm is destroyed you can reopen it can using managed_shared_memory shm(open_only, "name", MemSize); Regards, Ion

Ion Gaztañaga wrote:
Sorry, I don't understand very well this point. Shared memory is never destroyed until explicitly removed, but it must be reopened if the shared_memory_object object is destroyed:
//Imagine a default constructor is available: managed_shared_memory global_shm;
void func(bool false_is_open_true_is_create) { if(false_is_open_true_is_create){ managed_shared_memory shm(create_only, "name", MemSize); global_shm.swap(shm); } else{ managed_shared_memory shm(open_only, "name", MemSize); global_shm.swap(shm); } //global_shm is usable here because it has been swapped //with a correctly opened instance global_shm.create<>(...); }
Why not simply delay the construction of the managed shared memory segment until you have the required arguments to initialize it? The original poster said he could have used pointers and dynamic memory allocation to achieve that, but that's a fairly inefficient way of doing it. It is perfectly possible to simply use a POD with compatible size and alignment as a global variable, then construct it as the desired object once possible. This is basically what boost::optional does, except it also uses a boolean to know whether destruction must be performed or not.

Mathias Gaunard wrote:
Why not simply delay the construction of the managed shared memory segment until you have the required arguments to initialize it?
The original poster said he could have used pointers and dynamic memory allocation to achieve that, but that's a fairly inefficient way of doing it. It is perfectly possible to simply use a POD with compatible size and alignment as a global variable, then construct it as the desired object once possible.
Yes but the destructor is not called.
This is basically what boost::optional does, except it also uses a boolean to know whether destruction must be performed or not.
Correct. But not everyone likes to use optional and pay the overhead of an additional bool that must be also stored inside managed_shared_memory (either explicitly or implicitly, via an internal state) to support move semantics. Some might not like to have a default-constructor that represents an empty class, but it's a design decision.For example, unique_lock: http://www.boost.org/doc/libs/1_37_0/doc/html/thread/synchronization.html#th... supports default construction and eases moving values in branches without using optional: unique_lock<mutex> u; //If thread-safe use a lock, avoid it otherwise. if(thread_safe){ u = move(unique_lock(internal_lock_)); } //thread-save code depending on the branch... Regards, Ion

On 11/30/08, Ion Gaztañaga <igaztanaga@gmail.com> wrote:
Jan Stetka wrote:
I'm don't known what "move semantics" are, my motive is so I can have named_mutex and managed_shared_memory in the global scope without having to instantiate as pointers.
In other words, you want to create a default-constructed type and then swap it with a named constructed one when you have enough data to build a named object?
IMHO for this purpose boost.optional + inplace factory is the best solution. -- gpd

Giovanni Piero Deretta wrote:
On 11/30/08, Ion Gaztañaga <igaztanaga@gmail.com> wrote:
Jan Stetka wrote:
I'm don't known what "move semantics" are, my motive is so I can have named_mutex and managed_shared_memory in the global scope without having to instantiate as pointers. In other words, you want to create a default-constructed type and then swap it with a named constructed one when you have enough data to build a named object?
IMHO for this purpose boost.optional + inplace factory is the best solution.
Yes, this is a good solution. However, some prefer having a default constructed state (that only can be modified through swapping or move semantics), since you already need it to implement a private "default-constructed state" to correctly implement the destructor. Regards, Ion

Ion Gaztañaga wrote:
Because in the review the two step construction (default ctor + open()) was considered harmful. I guess now it would be useful in the presence of move semantics.
If you implement move as swap, there is no problem at all. You could still implement move as transfer, leaving the moved-from object in some state where only destruction and assignment are valid; there is no need to make that state reachable from public default-construction.

Mathias Gaunard wrote:
If you implement move as swap, there is no problem at all. You could still implement move as transfer, leaving the moved-from object in some state where only destruction and assignment are valid; there is no need to make that state reachable from public default-construction.
I don't think move should be implemented as swap, because the resource (shared memory) is still there floating around: //other_shm poits now to shm resources? shm = move(other_shm); I think move should be implemented as containers implement it (something similar to swap + clear): //other_shm will be in default-constructed state shm = move(other_shm) Regards, Ion

Ion Gaztañaga wrote :
I don't think move should be implemented as swap, because the resource (shared memory) is still there floating around:
Indeed, but that is the only way to really keep the strong invariant of one-way construction (which certainly is useful, since it allows optimal object performance by avoiding tests for "emptiness" and leads to safer code since operations on empty objects may not happen). Using swap however may lead to delayed resource reclamation in expressions introducing a lot of temporaries, which is the problem you're refering to (albeit you're using casting to turn named variables into temporaries, meaning the expression is their whole scope). I'm not so sure it is that big an issue however, but your opinion may differ on that subject, hence the alternative solution below. Ideally, we would want destructive move semantics (move semantics where the moved-from object is destructed when move is performed) but those are not what C++0x provides, unfortunately.
//other_shm will be in default-constructed state shm = move(other_shm)
Why must that state be the default-constructed one? It could just be an unspecified private state than can only be reached from a move, and where only the minimum required operations, such as destruction, are valid. However, that means potential overhead for those operations. And I personally still do not know what operations are required to be valid on moved-from objects, since that is badly specified, which means in the worst case you have to support all assignment and copying scenarios from/to moved-from objects. That still provides all the safety of the original not move-aware solution, but only if the user is careful about what he does to named variables he has casted to temporaries once they have been moved,; seems safe enough. There is overhead, but only less than with allowing two-phase construction, unless the rollback semantics are hard to maintain on assignment due to exception safety issues (you might also choose not to support them here and fall back to the moved state on failure). You talked of containers; but since those are dynamically-sized sequences of elements, it makes sense for them to be empty, i.e. to be a sequence of 0 element. On the other hand, for a lot of other things, being empty doesn't make much sense.

Mathias Gaunard wrote:
Ion Gaztañaga wrote :
I don't think move should be implemented as swap, because the resource (shared memory) is still there floating around:
Indeed, but that is the only way to really keep the strong invariant of one-way construction (which certainly is useful, since it allows optimal object performance by avoiding tests for "emptiness" and leads to safer code since operations on empty objects may not happen).
Yes but this might be surprising to the user, specially when objects are stored in containers and the user does not "see" the move operation.
Ideally, we would want destructive move semantics (move semantics where the moved-from object is destructed when move is performed) but those are not what C++0x provides, unfortunately.
Destructive move semantics would be nice to have, but I think they are complementary to non-destructive ones and ideally I would like to have compiler support to detect "dangling references" (moved, thus destroyed objects) at compile time.
//other_shm will be in default-constructed state shm = move(other_shm)
Why must that state be the default-constructed one? It could just be an unspecified private state than can only be reached from a move, and where only the minimum required operations, such as destruction, are valid.
Correct. It's a design decision.
However, that means potential overhead for those operations. And I personally still do not know what operations are required to be valid on moved-from objects, since that is badly specified, which means in the worst case you have to support all assignment and copying scenarios from/to moved-from objects.
I think there is no overhead. If a private state can be achieved representing "empty object" you can make it public.
There is overhead, but only less than with allowing two-phase construction, unless the rollback semantics are hard to maintain on assignment due to exception safety issues (you might also choose not to support them here and fall back to the moved state on failure).
I understand your points. And I appreciate the one-construction invariant. But that invariant will be broken with move semantics (because they are non-destructive) and you can have non-operational objects. In practice, I've found default constructors useful for non-copyable elements, because they make some code easier without adding any penalty (because the internal "moved" state must exist) and the default constructor makes your code easier to write when branches are around. Imagine a class that has a movable-only type: class MyClass { private: movable_only mo_; }; If I want to implement two-phase construction (for whatever reason) I need to use optional or use heap allocation (in some situations dynamic memory is not available, in embedded systems or when you want to place the object inside shared memory). I also need to initialize the member in the constructor argument list, which is really annoying if I need to do some non-trivial calculations to obtain the data to construct the member: MyClass () : mo_(/*non-trivial code should go here to initialize mo_*/) { //... }; Of course, I could call an static member that returns some data, but I would need to repeat that for every argument or mo_). With default constructors I can do this: MyClass () : mo_() { //Non trivial code... //... //I have all the data to correctly construct mo_ mo_ = move(movable_only(...)); }; Another option would be to call an static function that returns a movable_only object: MyClass () : mo_(static_create_movable_only_from_data(...)) {}; but this can be more costly than the default constructed case, because I need to forward all the data to the static function. I'm sure you can workaround these problems, but I think they complicate coding. Regards, Ion

Ion Gaztañaga wrote:
I think there is no overhead.
The overhead I'm talking about is that allowing an empty state in the object might have a cost. A simple example is that if you have a function release(resource&), that releases a valid resource, and you want to wrap that resource using RAII, you have to write the destructor as if(!is_empty) release(res); instead of simply release(res); So you need to maintain a boolean (size overhead) and perform a branching (runtime overhead). Usually, however, at least the size overhead can be eliminated since there is a special resource value reserved for the null resource.

Mathias Gaunard wrote:
The overhead I'm talking about is that allowing an empty state in the object might have a cost.
A simple example is that if you have a function release(resource&), that releases a valid resource, and you want to wrap that resource using RAII, you have to write the destructor as
if(!is_empty) release(res);
instead of simply
release(res);
So you need to maintain a boolean (size overhead) and perform a branching (runtime overhead).
Usually, however, at least the size overhead can be eliminated since there is a special resource value reserved for the null resource.
You are right. My point is that if you want to support a movable object: { non_copyable_file_descriptor fd("filename"); non_copyable_file_descriptor fd2(move(fd)); //Both destroyed here //fd2 closes the file descriptor //fd is "empty" } you need to encode the "empty" state. Making the default constructor public or not is not the point. If we want to support non-destructible move-semantics we will always have this overhead in destructors. There is a proposal to optimize these destructors: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2754.html N2754: "Additional concepts: TriviallyDestructibleAfterMove and TriviallyReallocatable" This will be very useful in containers, but I'm just thinking if compilers could be smart enough to avoid calling destructors for moved objects if the are TriviallyDestructibleAfterMove: { //non_copyable_file_descriptor is //"TriviallyDestructibleAfterMove": non_copyable_file_descriptor fd("filename"); non_copyable_file_descriptor fd2(move(fd)); //Both destroyed here //fd2 closes the file descriptor //The compiler bypasses fd's destructor } Regards, Ion

on Wed Dec 03 2008, Ion Gaztañaga <igaztanaga-AT-gmail.com> wrote:
Mathias Gaunard wrote:
The overhead I'm talking about is that allowing an empty state in the object might have a cost.
A simple example is that if you have a function release(resource&), that releases a valid resource, and you want to wrap that resource using RAII, you have to write the destructor as
if(!is_empty) release(res);
instead of simply
release(res);
So you need to maintain a boolean (size overhead) and perform a branching (runtime overhead).
Usually, however, at least the size overhead can be eliminated since there is a special resource value reserved for the null resource.
You are right. My point is that if you want to support a movable object:
{ non_copyable_file_descriptor fd("filename"); non_copyable_file_descriptor fd2(move(fd)); //Both destroyed here //fd2 closes the file descriptor //fd is "empty" }
you need to encode the "empty" state. Making the default constructor public or not is not the point. If we want to support non-destructible move-semantics we will always have this overhead in destructors.
It is true that we need an empty state for move semantics. However, the appearance of said state can naturally be restricted to situations where the object is about to be either destroyed or assigned, which means that it's reasonable to say that the object's other operations are only available when the state isn't empty. It's somewhat less-reasonable to say that other operations are unavailable for empty objects when the empty state is available via a constructor (although the built-ins do it). I know it's a little wishy-washy, but I think limiting construction to producing fully-capable objects can still be valuable even in the face of an empty state introduced for move semantics.
There is a proposal to optimize these destructors:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2754.html
N2754: "Additional concepts: TriviallyDestructibleAfterMove and TriviallyReallocatable"
This will be very useful in containers, but I'm just thinking if compilers could be smart enough to avoid calling destructors for moved objects if the are TriviallyDestructibleAfterMove:
{ //non_copyable_file_descriptor is //"TriviallyDestructibleAfterMove": non_copyable_file_descriptor fd("filename"); non_copyable_file_descriptor fd2(move(fd)); //Both destroyed here //fd2 closes the file descriptor //The compiler bypasses fd's destructor }
That would be kewl. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

on Sun Nov 30 2008, Mathias Gaunard <mathias.gaunard-AT-ens-lyon.org> wrote:
Ideally, we would want destructive move semantics (move semantics where the moved-from object is destructed when move is performed) but those are not what C++0x provides, unfortunately.
I don't think it's unfortunate. Destructive move semantics is nearly impossible to use correctly in many conditions. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

on Wed Dec 03 2008, Mathias Gaunard <mathias.gaunard-AT-ens-lyon.org> wrote:
David Abrahams wrote:
I don't think it's unfortunate. Destructive move semantics is nearly impossible to use correctly in many conditions.
Would you mind giving examples?
void f() { X a; if (something) g(move(a)); // ... // a.~X() ? } -- Dave Abrahams BoostPro Computing http://www.boostpro.com

David Abrahams wrote:
on Wed Dec 03 2008, Mathias Gaunard <mathias.gaunard-AT-ens-lyon.org> wrote:
David Abrahams wrote:
I don't think it's unfortunate. Destructive move semantics is nearly impossible to use correctly in many conditions. Would you mind giving examples?
void f() { X a; if (something) g(move(a)); // ...
// a.~X() ? }
Indeed, in the general case, the compiler (this certainly shouldn't be the job of the programmer) would have to transform the code to something like that: void f() { X a; bool destruct_a = true; if (something) { g(move(a)); destruct_a = false; } // ... if (destruct_a) a.~X(); } Usual const-propagation should be enough to optimize those booleans out when possible. It's true however the current system is really much simpler since there is no such work required for the compiler (and maybe it can be optimized just as well?)

On Fri, Dec 5, 2008 at 4:00 PM, Mathias Gaunard <mathias.gaunard@ens-lyon.org> wrote:
void f() { X a; bool destruct_a = true;
if (something) { g(move(a)); destruct_a = false; } // ...
if (destruct_a) a.~X(); }
Usual const-propagation should be enough to optimize those booleans out when possible.
I don't see how const-propagation is relevant here, but regardless -- const-propagation rarely (if ever) leads to optimization possibilities. Emil Dotchevski Reverge Studios, Inc. http://www.revergestudios.com/reblog/index.php?n=ReCode

Emil Dotchevski wrote:
I don't see how const-propagation is relevant here, but regardless -- const-propagation rarely (if ever) leads to optimization possibilities.
It is certainly one of the most (if not the most) important optimization in a compiler. http://en.wikipedia.org/wiki/Constant_folding sparse conditional constant propagation is probably quite necessary for good propagation across branches.

On Fri, Dec 5, 2008 at 4:59 PM, Mathias Gaunard <mathias.gaunard@ens-lyon.org> wrote:
Emil Dotchevski wrote:
I don't see how const-propagation is relevant here, but regardless -- const-propagation rarely (if ever) leads to optimization possibilities.
It is certainly one of the most (if not the most) important optimization in a compiler. http://en.wikipedia.org/wiki/Constant_folding
sparse conditional constant propagation is probably quite necessary for good propagation across branches.
Ah I misunderstood your point, but even so, this is tricky stuff. For example, floating point constant expressions are sometimes evaluated at run-time. But in this particular case, the compiler can't always see the "constness" of the if. You can argue that "whole program optimization" kind of stuff can help, but the thing is, it can help even if by definition the move semantics are not destructive. :) Emil Dotchevski Reverge Studios, Inc. http://www.revergestudios.com/reblog/index.php?n=ReCode

on Fri Dec 05 2008, Mathias Gaunard <mathias.gaunard-AT-ens-lyon.org> wrote:
David Abrahams wrote:
on Wed Dec 03 2008, Mathias Gaunard <mathias.gaunard-AT-ens-lyon.org> wrote:
David Abrahams wrote:
I don't think it's unfortunate. Destructive move semantics is nearly impossible to use correctly in many conditions. Would you mind giving examples?
void f() { X a; if (something) g(move(a)); // ... // a.~X() ? }
Indeed, in the general case, the compiler (this certainly shouldn't be the job of the programmer) would have to transform the code to something like that:
void f() { X a; bool destruct_a = true;
if (something) { g(move(a)); destruct_a = false; } // ...
if (destruct_a) a.~X(); }
In general the compiler can't know. void f(X& a) { if (something) g(move(a)); } void h() { X a; f(a); } Separate translation units if you must. Challenge your own idea a little bit; you'll see where this leads. Destruction without static knowledge thereof is the ultimate violation of invariants. -- Dave Abrahams BoostPro Computing http://www.boostpro.com
participants (6)
-
David Abrahams
-
Emil Dotchevski
-
Giovanni Piero Deretta
-
Ion Gaztañaga
-
Jan Stetka
-
Mathias Gaunard