私有构造函数禁止使用 emplace[_back]() 来避免移动
Posted
技术标签:
【中文标题】私有构造函数禁止使用 emplace[_back]() 来避免移动【英文标题】:Private constructor inhibits use of emplace[_back]() to avoid a move 【发布时间】:2012-07-10 15:40:45 【问题描述】:考虑以下代码:
#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
;
由于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
添加一个friend-declaration,但是准确的类需要是A
的朋友(也就是实际尝试调用构造函数的类)是特定于实现的(例如,对于 GCC,它是 __gnu_cxx::new_allocator<A>
)。 编辑:刚刚意识到这样的朋友声明将允许任何人将emplace_back()
A
用私有构造函数构造成A
的容器,所以它不会真正解决任何问题,我还不如将构造函数公开......
更新:我应该补充一点,A
的移动构造函数昂贵并不是避免调用它的唯一原因。 A
可能根本不能移动(也不能复制)。当然,这不适用于vector
(因为emplace_back()
可能必须重新分配向量),但它适用于deque
,它也有类似的emplace_back()
方法,但不必重新分配任何东西。
【问题讨论】:
旁注,但移动构造函数应该是最快的构造形式……听起来像是一个设计问题。 @GManNickG:使用push_back
将产生私有构造函数的成本 + 移动构造函数的成本。使用emplace_back
只会产生私有构造函数的成本,所以它会更快。
@GManNickG:另外,避免调用移动构造函数的原因比避免其成本的原因更多。例如,A 可能根本无法移动构造(假设我将 vector
替换为 deque
- 这将是一个合理的场景)。我将编辑答案以提及这一点。
@GManNickG:最后,移动构造函数可能必须很昂贵是有原因的。例如,考虑一个聚合大型 std::array
成员的类。
@GManNickG:按值返回向量不会不会导致调用每个元素的移动构造函数。将调用向量的移动构造函数,它将简单地复制向量的内部指针/大小成员,并且根本不接触元素。
【参考方案1】:
一种可能的解决方法(或 kludge)是使用辅助类将参数保存到 A
的私有 ctor(我们将此类称为 EmplaceHelper
)。 EmplaceHelper 还应该有一个私人 ctor,并且它应该与 A
相互友好。现在你所需要的只是 A 中的一个公共 ctor,它接受这个 EmplaceHelper
(可能是通过 const-ref),并将它与 emplace_back(EmplaceHelper(...))
一起使用。
由于 EmplaceHelper
只能由 A
构造,因此您的公共 ctor 实际上仍然是私有的。
甚至可以使用模板化的 EmplaceHelper 来概括这个想法(可能使用 std::tuple
来保存 ctor 参数)。
编辑: 实际上,我似乎把它复杂化了,因为 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)
,只使用新版本。
【讨论】:
感谢 GManNickG 对其进行润色 :) 我在上班途中在地铁上制定了答案,当时无法闪出示例代码;)【参考方案2】:根据 C++11 标准,所有标准容器都应该使用allocator::construct
方法来进行就地构造。因此,您可以简单地将std::allocator
设为A
的朋友。
我想从技术上讲,这个函数可以将实际的构造调用委托给其他东西。就个人而言,我认为规范应该更加严格地强制执行哪些对象调用构造函数以及哪些可以委托和不能委托。
如果发生这种委派,或者出于任何原因,您可以提供自己的分配器,将所有调用转发到std::allocator
,construct
除外。我不建议使用后者,因为许多标准容器实现都有处理std::allocator
的特殊代码,这使得它们占用的空间更少。
刚刚意识到这样一个朋友声明将允许任何人将使用私有构造函数构造的 A emplace_back() 放入 A 的容器中,所以它不会真正解决任何问题,我还不如在那时将构造函数公开。 ..
然后你将不得不决定什么对你来说更重要:就地建造或隐藏私人。就其本质而言,就地构造意味着代码中的某人正在执行构造。因此,没有办法绕过它:某些外部代码必须被命名为朋友,或者构造函数必须是公共的。简而言之,构造函数必须公开可供任何被委托构造的人访问。
【讨论】:
鉴于我们在 C++ 中的非传递友谊,我同意应该指定构造方法的观点。 My answer there 可能会对这个问题有所了解。它不适用于带有标准分配器的标准双端队列(需要子类化),但证明它可以使用自定义分配器完成(但我也必须覆盖rebind
)。这里接受的答案更符合作者的要求,所以,我不会把我的解决方案放在这里,只是the link。【参考方案3】:
让我们简化一下。以下无法编译,因为 V 无法访问 A 的私有构造函数。
struct V
E(int i)
// ...
auto a = A(i);
// ...
;
回到您的代码,V 只是向量的简化,而 V::E 只是 emplace_back 所做的简化。 vector 无权访问 A 的私有构造函数,vector::emplace_back 需要调用它。
【讨论】:
我理解它为什么不起作用的机制。我只是在寻找解决方法。 @HighCommander4:不幸的是,没有很好的解决方法,因为 C++friend
是非常临时的。正如您所指出的,您可以将其公开,但如果您希望它更明确一点,例如“嘿,我正在使用这个函数,我们想要私有但不能!”,您可以做一个这样的技巧:struct foo struct using_private_constructor ; foo(int, const using_private_constructor&); ;
,现在呼叫站点看起来像:foo f(5, foo::using_private_constructor())
,或result.emplace_back(3, foo::using_private_constructor())
。不过,也许你可以证明让它成为普通公众是合理的。以上是关于私有构造函数禁止使用 emplace[_back]() 来避免移动的主要内容,如果未能解决你的问题,请参考以下文章
学习 emplace_back() 和 push_back 的区别 emplace_back效率高
emplace_back() 和 push_back 的区别
std::emplace_back 调用向量中其他对象的构造函数?