Частный конструктор запрещает использование emplace [_back](), чтобы избежать перемещения

Рассмотрим следующий код:

#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
};

Так как конструктор move A несколько дорог (по какой-либо причине), я бы хотел не называть его и вместо него использовать emplace_back():

#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
};

К сожалению, при emplace_back() фактический вызов конструктора выполняется чем-то в стандартной библиотеке, которая недостаточно привилегирована, чтобы иметь возможность вызывать частный конструктор A.

Я понимаю, что, возможно, мало что можно сделать по этому поводу, но, тем не менее, я чувствую, что, поскольку вызовы emplace_back() встречаются внутри члена A, они должны иметь возможность вызвать частный конструктор.

Есть ли какие-либо обходные пути для этого?

Единственное, что я могу придумать, это добавить объявление друга в A, но точный класс, который должен быть A friend (то есть класс, который на самом деле пытается вызвать конструктор), - это реализация (например, для GCC it __gnu_cxx::new_allocator<A>). РЕДАКТИРОВАТЬ: только что понял, что такое объявление друга позволит любому emplace_back() A, сконструированному с помощью частного конструктора, в контейнер A, поэтому он ничего не решит, Я мог бы также сделать конструктор открытым в этот момент...

UPDATE. Я должен добавить, что конструктор перемещения A, являющийся дорогостоящим, не является единственной причиной, почему бы не называть его. Возможно, что A вообще не перемещается (и не копируется). Это, конечно, не работает с vector (потому что emplace_back() может потребоваться перераспределить вектор), но это будет с deque, который также имеет аналогичный метод emplace_back(), но не должен перераспределять что-либо.

Ответ 1

Одним из возможных путей решения этой проблемы (или kludge) было бы использование вспомогательного класса для хранения параметров в частном ctor элемента A (пусть этот класс EmplaceHelper). У EmplaceHelper также должен быть личный ctor, и он должен быть во взаимной дружбе с A Теперь все, что вам нужно, это публичный ctor в A, который берет этот EmplaceHelper (вероятно, через const-ref) и использует его с emplace_back(EmplaceHelper(...)).

Поскольку EmplaceHelper может быть EmplaceHelper только A, ваш общедоступный ctor по-прежнему остается закрытым.

Можно даже обобщить эту идею с помощью шаблонного EmplaceHelper (возможно, используя std::tuple для хранения параметров ctor).

Edit: на самом деле, мне кажется, я усложненное это как ниже комментарий от GManNickG дал мне более простую идею: добавить частный вспомогательный класс (private_ctor_t в данном примере), что это просто пустой класс, но так как это частная она доступна только. A Изменить A конструктор включить этот частный класс как первый (или последний) параметр (и обнародует его). В результате только A может конструировать себя так, как если бы у него был частный конструктор, но эта конструкция теперь может быть легко делегирована.

Как это:

#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 */ }
}; 

Если делегированные конструкторы недоступны, вы можете либо выделить общий код для каждой версии, либо просто полностью избавиться от A(int) и использовать только новую версию.

Ответ 2

В стандарте С++ 11 все стандартные контейнеры должны использовать метод allocator::construct для создания на месте. Таким образом, вы можете просто сделать std::allocator другом A.

Я предполагаю, что технически этой функции разрешено делегировать вызов реальной конструкции другому. Лично я считаю, что спецификация должна быть немного более строгой в том, чтобы обеспечить точно, какие объекты вызывают конструкторы и что может и не может быть делегировано.

Если такое делегирование происходит или по какой-либо причине, вы можете предоставить свой собственный распределитель, который переадресует все вызовы на std::allocator, за исключением construct. Я не предлагаю последнего, так как многие стандартные реализации контейнеров имеют специальный код для работы с std::allocator, который позволяет им занимать меньше места.

просто понял, что такое объявление друга позволит любому emplace_back() A, сконструированному с помощью частного конструктора, в контейнер A, поэтому он ничего не решит, я мог бы также сделать конструктор общедоступным в этот момент...

Тогда вам придется решать, что для вас важнее: строительство на месте или скрытие рядовых. По своей природе, на месте строительства означает, что кто-то не в вашем коде делает строительство. Поэтому нет никакого пути: какой-то внешний код должен быть назван другом или конструктор должен быть общедоступным. Короче говоря, конструктор должен быть общедоступным для тех, кому делегируется конструкция.

Ответ 3

Немного упростим. Не удается скомпилировать следующее: V не имеет доступа к частному конструктору.

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

Возвращаясь к вашему коду, V - просто упрощение вектора, а V:: E - просто упрощение того, что делает emplace_back. вектор не имеет доступа к частному конструктору, а vector:: emplace_back необходимо вызвать.