Am 01.05.2012 20:29, schrieb Jason McKesson:
>
> On Sunday, April 29, 2012 11:59:51 PM UTC-7, Daniel Krügler wrote:
>>
>> Am 29.04.2012 02:02, schrieb Jason McKesson:
>> [..]
>>>
>>>
>>> It's not so much a problem as wondering how this is supposed to work.
>>> Without the ability to have `make_shared` be a friend (in a useful
>>> way), it is impossible to *force* users to use `make_shared`.
>>
>>
>>
>> I don't understand your question: Surely it cannot be the
>> responsibility of a library implementation to enforce user code to use
>> some particular component.
>
>
> A good API makes it difficult if not impossible to do things
> incorrectly. And if a particular object has `enable_shared_from_this`
> as a base class, then it is incorrect to create an instance of that
> object *without* a shared_ptr.
>
> So you have to either force the user to use `make_shared` or force the
> user to use factory functions. Enforcing either requires the use of
> private constructors.
I disagree with your conclusion. There exist well-known strategies to
handle this situation without making the constructor itself private.
As often the case, one further indirection can solve the problem. A
simple one is add an "access-point" parameter to any constructor. This
access-point can only be created by your own factory function which
again invokes make_shared. Something along the lines of [pseudo-code]
class YourPrivacy : public std::enable_shared_from_this<YourPrivacy> {
public:
class AccessPoint {
AccessPoint(){}
friend std::shared_ptr<YourPrivacy> make_ptr(Args...);
};
YourPrivacy(AccessPoint, Args...);
};
inline std::shared_ptr<YourPrivacy> make_ptr(Args... args) {
return std::make_shared<YourPrivacy>(AccessPoint(), args...);
}
>>> You also can't effectively combine factory functions with
>>> `make_shared`. So you lose a lot of the benefits of them when dealing
>>> with factories.
>>
>>
>>
>> I don't understand what you are trying to say here.
>
>
> See above. You usually enforce the use of factory functions by making
> constructors private.
Or introduce an access-point parameter as shown above.
>>> It shouldn't be too much of a burden on implementations to force them
>>> to do the final construction of the type within `make_shared` itself,
>>> rather than in some object they create.
>>
>>
>>
>> Even if some particular implementations decides to do so, this won't be
>> portable code anymore. Standardizing this for this special function
>> template would also seem very odd, a more general policy should be
>> considered, if needed.
>
>
> Extending it to `emplace` member functions wouldn't be a bad idea.
This can be solved by the same kind of access-point technique.
>> In regard to the "burden" I think you are
>> mislead. The guarantee that the actual code is called in a particular
>> location is *not* sufficient. Todays high-quality implementations of
>> the Standard Library usually provide a lot of static concept-checking
>> to ensure that user-types satisfy the requirements of the standard.
>> One requirement is that
>>
>> "The expression ::new (pv) T(std::forward<Args>(args)...), where pv
>> has type void* and points to storage suitable to hold an object of
>> type T, shall be well formed."
>>
>> If a static concept-checking tool attempts to validate this it won't
>> help that you have assigned friendship to the function: The
>> static-tool machinery would also require this friendship.
>
>
> The wording would have to be changed. That expression would be valid,
> but *only* within the make_shared function itself. So any concept
> checking (if it's still possible) would have to be done within the
> function, not in extra classes and so forth.
I don't think that such wording extensions will help you. Let me give
one further example why I think that asking of "befriendable" library
components is a bad idea:
Real-life libraries are not immediately C++11 ready, so they may have
incomplete support for variadic templates. They could still provide a
generally usable library by emulating the variadics by a series of
overloaded templates with increasing argument length.
Your friend-ship technique requires *exact* knowledge of the signature
of the library-component. This is a very bad dependency on your
concrete library implementation.
>>> Also, allocate_shared won't help, because that's about the allocation
>>> of the block of memory, not the calling of the constructor.
>>
>>
>>
>> Yes, the current wording is defective in this regard, but the proposed
>> resolution of
>>
>>
http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#2070
>>
>> would ensure that the allocator's construct and destroy function will
>> be used. gcc 4.8 has already implemented the P/R for evaluation
>> purposes.
>
>
> True, but that requires a *lot* of boilerplate to do something that
> ought to be very, very simple. You have to make a special allocator to
> be used for instances of the class, and most of its methods will just
> call the std::allocator function. It requires the user to use the
> less-well-known `allocate_shared` instead of `make_shared`. It's less
> idiomatic.
My previous example shows that it is possible to realize your needs
also with make_shared. Nonetheless I disagree that allocate_shared
should be considered as less idiomatic. In fact, allocators in C++11
are a real key-point for access-control *and* construction-destruction
control in many places in the library. In fact, the allocator-based
containers defer all construction issues to the allocator, therefore
new requirement names have been introduced like Copy/MoveInsertable,
and EmplaceConstructible, and most recently DefaultInsertable and
Erasable. Even if you *can* solve your problem with make_shared, it
might be worth to look at an allocator-based approach, because it
provides other advantages. E.g. it would allow you more easily to get
a statistics of allocated objects or you can insert dedicated
pre/post-construction/destruction code parts (aspect-oriented).
Further, the needed boiler-plate to create a fully functional
allocator has been dramatically reduced in C++11. The following
interface is sufficient (more or less stolen from the standard):
template <class T>
struct SimpleAllocator {
typedef T value_type;
SimpleAllocator(/some args/);
template <class U> SimpleAllocator(const SimpleAllocator<U>& other);
T* allocate(std::size_t n);
void deallocate(T* p, std::size_t n);
};
template <class T, class U>
bool operator==(const SimpleAllocator<T>&, const SimpleAllocator<U>&);
template <class T, class U>
bool operator!=(const SimpleAllocator<T>&, const SimpleAllocator<U>&);
HTH & Greetings from Bremen,
Daniel Krügler