Частный конструктор запрещает использование 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 необходимо вызвать.