Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Private constructor inhibits use of emplace[_back]() to avoid a move

Consider the following code:

#include <vector>

class A
{
public:    
    A(A&&);  // somewhat expensive

    static std::vector<A> make_As()
    {
        std::vector<A> result;
        result.push_back(A(3));
        result.push_back(A(4));
        return result;
    }

private:
    A(int);  // private constructor
};

Since A's move constructor is somewhat expensive (for whatever reason), I'd like to avoid calling it and use emplace_back() instead:

#include <vector>

class A
{
public:    
    A(A&&);  // somewhat expensive

    static std::vector<A> make_As()
    {
        std::vector<A> result;
        result.emplace_back(3);
        result.emplace_back(4);
        return result;
    }

private:
    A(int);  // private constructor
};

Unfortunately, with emplace_back(), the actual constructor call is done by something in the standard library, which is not privileged enough to be able to call A's private constructor.

I realize that there's probably little that can be done about this, but nonetheless I feel that since the calls to emplace_back() occur within a member of A, they ought to be able to call the private constructor.

Are there any workarounds for this?

The only thing I can think of is to add a friend-declaration to A, but the precise class that needs to be A's friend (that is, the class that actually tries to invoke the constructor) is implementation-specific (for example, for GCC it's __gnu_cxx::new_allocator<A>). EDIT: just realized that such a friend declaration will allow anyone to emplace_back() A's constructed with the private constructor into a container of A's, so it wouldn't really solve anything, I might as well make the constructor public at that point...

UPDATE: I should add that A's move constructor being expensive is not the only reason to avoid having to call it. It may be that A is not movable at all (nor copyable). That would not work with vector, of course, (because emplace_back() may have to reallocate the vector), but it would with deque, which also has a similar emplace_back() method but does not have to reallocate anything.

like image 816
HighCommander4 Avatar asked Jul 11 '12 04:07

HighCommander4


People also ask

What is the function of Emplace_back?

This function is used to insert a new element into the vector container, the new element is added to the end of the vector. Syntax : vectorname. emplace_back(value) Parameters : The element to be inserted into the vector is passed as the parameter.

Does Emplace_back copy or move?

This code demonstrates that emplace_back calls the copy constructor of A for some reason to copy the first element. But if you leave copy constructor as deleted, it will use move constructor instead.

Should I use Push_back or Emplace_back?

Specific use case for emplace_back : If you need to create a temporary object which will then be pushed into a container, use emplace_back instead of push_back . It will create the object in-place within the container. Notes: push_back in the above case will create a temporary object and move it into the container.

Which is better emplace or push?

The difference in the efficiency of push_back and emplace_back depends on the type of our vector. If the vector is a built-in type, there is no difference between the efficiency of push_back and emplace_back. If the vector type is class or struct, emplace_back is more efficient than push_back.


3 Answers

One possible workaround (or kludge) would be to use a helper class to hold the parameters to the private ctor of A (let's call this class EmplaceHelper). EmplaceHelper should also have a private ctor, and it should be in mutual friendship with A. Now all you need is a public ctor in A that takes this EmplaceHelper (via const-ref, probably), and use that with emplace_back(EmplaceHelper(...)).

Since EmplaceHelper can only be constructed by A, your public ctor is still effectively private.

It might even be possible to generalize this idea with a templated EmplaceHelper (possibly using std::tuple to hold the ctor parameters).

Edit: actually, it seems I overcomplicated this as a comment below from GManNickG gave me a simpler idea: add a private helper class (private_ctor_t in the example) that is just an empty class but since it is private it is only accessible by A. Modify A's constructor to include this private class as the first (or last) parameter (and make it public). The effect would be that only A could construct itself as if it had a private constructor, but this construction could be easily delegated now.

Like this:

#include <vector>
class A 
{ 
private:
    struct private_ctor_t {};

public:     
    A(private_ctor_t, int x) : A(x) // C++11 only, delegating constructor
    {}

    A(A&&) { /* somewhat expensive */ }

    static std::vector<A> make_As() 
    { 
        std::vector<A> result; 
        result.emplace_back(private_ctor_t{}, 3); 
        result.emplace_back(private_ctor_t{}, 4); 
        return result; 
    } 

private: 
    A(int) { /* private constructor */ }
}; 

If delegated constructors are not available, you can either factor out the common code for each version, or just get rid of A(int) altogether and only use the new version.

like image 130
mitchnull Avatar answered Oct 26 '22 17:10

mitchnull


By the C++11 standard, all of the standard containers should use the allocator::construct method to do in-place construction. As such, you could simply make std::allocator a friend of A.

I suppose technically this function is allowed to delegate the actual construction call to something else. Personally, I think the spec should be a little more strict about enforcing exactly which objects call constructors and what can and cannot be delegated.

If such delegation occurs, or for whatever reason, you could provide your own allocator that forwards all calls to std::allocator except for construct. I don't suggest the latter, as many standard container implementations have special code for dealing with std::allocator that allows them to take up less space.

just realized that such a friend declaration will allow anyone to emplace_back() A's constructed with the private constructor into a container of A's, so it wouldn't really solve anything, I might as well make the constructor public at that point...

Then you're going to have to decide what's more important to you: in-place construction, or hiding privates. By it's very nature, in-place construction means that someone not in your code is doing the construction. Therefore, there's no way around it: some external code must be named a friend or the constructor must be public. In short, the constructor must be publicly accessible to whomever is delegated the construction.

like image 10
Nicol Bolas Avatar answered Oct 26 '22 15:10

Nicol Bolas


Let's simplify a bit. The following fails to compile, because V has no access to A's private constructor.

struct V
{
    E(int i)
    {
        // ...
        auto a = A(i);
        // ...
    }
};

Going back to your code, V is just a simplification of vector, and V::E is just a simplification of what emplace_back does. vector doesn't have access to A's private constructor, and vector::emplace_back needs to call it.

like image 2
Nevin Avatar answered Oct 26 '22 15:10

Nevin