创建一个 class 的所有构造函数的类型列表
Creating a type list of all constructors of a class
我正在尝试实现惰性初始化类 lazy<T>
,它存储 T
的构造参数并在第一次需要时使用它们构造 T
。目前我正在捕获一个函数对象(存储在 boost::function
中)中的参数,该函数对象在调用时在指定位置构造对象;虽然这可行,但它需要动态分配,我想避免这种情况。
我想使用 boost::variant
,其中不同的变体是对应于 T
的各种构造函数(假设没有任何模板构造函数)和 T
本身,以便在编译时知道所需的内存量。为此,我需要一种编译时方法来 assemble T
中所有可访问构造函数的列表。有办法吗?
这个答案有2个半版本:
- 基于
variant<>
、
- 基于
optional<>
和
- 一个实际上不采用构造函数参数但采用可调用工厂的版本。这可能会减少动态分配(但与问题有点偏差)
增强变量
更新 感谢@5gon12eder 的评论,我创建了这个基于 Boost Variant 的更优雅的版本:
请注意,Boost Variant 也不使用动态内存分配。
#include <functional>
#include <boost/variant.hpp>
template <typename T>
struct Lazy {
template <typename... Args> Lazy(Args const&... args)
: _storage(Factory([=] { return T(args...); }))
{ }
T& get() { return ensure(); }
T const& get() const { return ensure(); }
explicit operator bool() const { return _storage.which(); }
private:
using Factory = std::function<T()>;
mutable boost::variant<Factory, T> _storage;
T& ensure() const {
if (!_storage.which())
_storage = boost::get<Factory>(_storage)();
return boost::get<T>(_storage);
}
};
#include <string>
#include <iostream>
int main() {
Lazy<std::string> ls("Hello world");
std::cout << ls.get() << "\n";
// Lazy<std::string> oops(42.7, true); // no matching function call
}
限制与 Boost Optional 一样(见下文)。请注意,对于变体,工厂将在实例化时被破坏,因此如果参数类型具有昂贵的析构函数,这可能是一个需要牢记的效率因素。
提升可选
When To Use Boost Optional
[...]
Another typical situation is to indicate that we do not have a value yet, but we expect to have it later. This notion can be used in implementing solutions like lazy initialization or a two-phase initialization.
请注意,Boost Optional 不使用动态内存分配。
#include <functional>
#include <boost/optional.hpp>
template <typename T>
struct Lazy {
template <typename... Args> Lazy(Args const&... args)
: _factory([=] { return T(args...); })
{ }
T& get() { return *(_value? _value : _value = _factory()); }
T const& get() const { return *(_value? _value : _value = _factory()); }
explicit operator bool() const { return _value; }
private:
std::function<T()> _factory;
mutable boost::optional<T> _value;
};
#include <string>
#include <iostream>
int main() {
Lazy<std::string> ls("Hello world");
std::cout << ls.get() << "\n";
// Lazy<std::string> oops(42.7, true); // no matching function call
}
诚然,这有局限性
- 没有正确推导大括号初始化列表,
- 构造函数参数必须是可复制的(使用 c++14 时可以不费吹灰之力解除此限制)
类型擦除的 function<>
可能仍会导致动态分配。一些实现针对小型工厂仿函数优化了它,使用小型对象缓冲区优化,所以 profile, profile, profile。
您可以考虑直接从调用者提供工厂仿函数:
请参阅此变体 Live On Coliru。
- 在某种程度上,这只是转移了负担——这可能重要也可能不重要
- 在呼叫站点上使用似乎不太友好
还请注意,它会及早检测构造函数参数,例如
Lazy<std::string> oops(42.7, true);
根本无法编译。
作为奖励,它具有到 bool 的类似智能指针的上下文转换。你会想要添加更多的东西(比如 hash<> 特化,可能还有只是遵从 boost::optionals
IO 运算符实现的 IO 运算符)
我正在尝试实现惰性初始化类 lazy<T>
,它存储 T
的构造参数并在第一次需要时使用它们构造 T
。目前我正在捕获一个函数对象(存储在 boost::function
中)中的参数,该函数对象在调用时在指定位置构造对象;虽然这可行,但它需要动态分配,我想避免这种情况。
我想使用 boost::variant
,其中不同的变体是对应于 T
的各种构造函数(假设没有任何模板构造函数)和 T
本身,以便在编译时知道所需的内存量。为此,我需要一种编译时方法来 assemble T
中所有可访问构造函数的列表。有办法吗?
这个答案有2个半版本:
- 基于
variant<>
、 - 基于
optional<>
和 - 一个实际上不采用构造函数参数但采用可调用工厂的版本。这可能会减少动态分配(但与问题有点偏差)
增强变量
更新 感谢@5gon12eder 的评论,我创建了这个基于 Boost Variant 的更优雅的版本:
请注意,Boost Variant 也不使用动态内存分配。
#include <functional>
#include <boost/variant.hpp>
template <typename T>
struct Lazy {
template <typename... Args> Lazy(Args const&... args)
: _storage(Factory([=] { return T(args...); }))
{ }
T& get() { return ensure(); }
T const& get() const { return ensure(); }
explicit operator bool() const { return _storage.which(); }
private:
using Factory = std::function<T()>;
mutable boost::variant<Factory, T> _storage;
T& ensure() const {
if (!_storage.which())
_storage = boost::get<Factory>(_storage)();
return boost::get<T>(_storage);
}
};
#include <string>
#include <iostream>
int main() {
Lazy<std::string> ls("Hello world");
std::cout << ls.get() << "\n";
// Lazy<std::string> oops(42.7, true); // no matching function call
}
限制与 Boost Optional 一样(见下文)。请注意,对于变体,工厂将在实例化时被破坏,因此如果参数类型具有昂贵的析构函数,这可能是一个需要牢记的效率因素。
提升可选
When To Use Boost Optional
[...]
Another typical situation is to indicate that we do not have a value yet, but we expect to have it later. This notion can be used in implementing solutions like lazy initialization or a two-phase initialization.
请注意,Boost Optional 不使用动态内存分配。
#include <functional>
#include <boost/optional.hpp>
template <typename T>
struct Lazy {
template <typename... Args> Lazy(Args const&... args)
: _factory([=] { return T(args...); })
{ }
T& get() { return *(_value? _value : _value = _factory()); }
T const& get() const { return *(_value? _value : _value = _factory()); }
explicit operator bool() const { return _value; }
private:
std::function<T()> _factory;
mutable boost::optional<T> _value;
};
#include <string>
#include <iostream>
int main() {
Lazy<std::string> ls("Hello world");
std::cout << ls.get() << "\n";
// Lazy<std::string> oops(42.7, true); // no matching function call
}
诚然,这有局限性
- 没有正确推导大括号初始化列表,
- 构造函数参数必须是可复制的(使用 c++14 时可以不费吹灰之力解除此限制)
类型擦除的
function<>
可能仍会导致动态分配。一些实现针对小型工厂仿函数优化了它,使用小型对象缓冲区优化,所以 profile, profile, profile。您可以考虑直接从调用者提供工厂仿函数:
请参阅此变体 Live On Coliru。- 在某种程度上,这只是转移了负担——这可能重要也可能不重要
- 在呼叫站点上使用似乎不太友好
还请注意,它会及早检测构造函数参数,例如
Lazy<std::string> oops(42.7, true);
根本无法编译。
作为奖励,它具有到 bool 的类似智能指针的上下文转换。你会想要添加更多的东西(比如 hash<> 特化,可能还有只是遵从 boost::optionals
IO 运算符实现的 IO 运算符)