每个对象单例
Per-object singleton
(我尝试搜索,但你只会得到大量简单的单例解释。)
“普通”单例保证在整个程序中只存在一个给定类型的对象。例如,像这样:
template <class T>
T& getSingleton()
{
static T instance;
return instance;
}
我正在寻找一种方法,在 O
类型的任何给定对象 中,给定类型 T
的对象不超过一个。也就是说,像这样:
class O
{
// getSingleton<SomeType>() always returns the same SomeType reference for the same instance of O.
// getSingleton<SomeType>() returns different SomeType references for different instances of O.
template<class T>
T& getSingleton() { /* ??? */ }
};
如果所有 T
都需要从某个基数 class 派生,这完全没问题(这可能是将它们类型擦除存储在 O
内的容器中所必需的) ,比如说,TBase
。但是,我们关心的 T
集合是开放的(即在定义 O
的地方不知道),因此成员变量不是解决方案。
当然有或多或少直接的存储解决方案,例如a std::vector<std::unique_ptr<TBase>>
并通过在每个元素上尝试 dynamic_cast
来实现 getSingleton<T>
。但我想知道是否有更优雅的方法,不需要线性时间。
标准库提供了std::type_index
,可用于有效地将唯一值关联到每个类型。此类型旨在用作关联容器的键。 std::unordered_map<std::type_index, std::any>
可用于包含任何类型的集合。由于知道该映射中任何元素的原始类型需要知道原始类型(以构造正确的 std::type_index
键),因此应该始终可以成功 any_cast
值。
这是一个示例实现 (godbolt) :
#include <any>
#include <typeindex>
#include <unordered_map>
class O
{
public:
template<class T>
T& getSingleton()
{
// Get a unique key for the type `T`
const auto key = std::type_index(typeid(T));
// Check if the element already exists
// If it doesn't, construct it in place
// In either case, returns an iterator to the object
auto[iter, unused] = members.try_emplace(key, std::in_place_type<T>);
// Get a reference to the value using its original type
return std::any_cast<T&>(iter->second);
}
private:
std::unordered_map<std::type_index, std::any> members;
};
用法如下:
#include <iostream>
int main()
{
O o;
// Will value initialize an `int`
int & i = o.getSingleton<int>();
// `i` and `j` refer to the same object
int & j = o.getSingleton<int>();
std::cout << i << ' ' << j << '\n';
// Changing `i` changes `j`
i = 42;
std::cout << i << ' ' << j << '\n';
}
此解决方案对T
的唯一要求是它默认可构造和可破坏。
可以使用类似这样的东西:
#include <map> // for map
#include <memory> // for unique_ptr
#include <tuple> // for ignore, tie
class O {
private:
template <class T>
static void index(){};
using obj_ptr = std::unique_ptr<void, void (*)(void*) noexcept>;
std::map<void (*)(), obj_ptr> instances;
public:
template <class T>
T& getSingleton() {
auto it = instances.find(index<T>);
if (it == instances.end()) {
static constexpr auto deleter =
+[](void* t) noexcept { delete static_cast<T*>(t); };
std::tie(it, std::ignore) =
instances.emplace(std::pair{&index<T>, obj_ptr{new T, deleter}});
}
return *static_cast<T*>(it->second.get());
}
};
(我尝试搜索,但你只会得到大量简单的单例解释。)
“普通”单例保证在整个程序中只存在一个给定类型的对象。例如,像这样:
template <class T>
T& getSingleton()
{
static T instance;
return instance;
}
我正在寻找一种方法,在 O
类型的任何给定对象 中,给定类型 T
的对象不超过一个。也就是说,像这样:
class O
{
// getSingleton<SomeType>() always returns the same SomeType reference for the same instance of O.
// getSingleton<SomeType>() returns different SomeType references for different instances of O.
template<class T>
T& getSingleton() { /* ??? */ }
};
如果所有 T
都需要从某个基数 class 派生,这完全没问题(这可能是将它们类型擦除存储在 O
内的容器中所必需的) ,比如说,TBase
。但是,我们关心的 T
集合是开放的(即在定义 O
的地方不知道),因此成员变量不是解决方案。
当然有或多或少直接的存储解决方案,例如a std::vector<std::unique_ptr<TBase>>
并通过在每个元素上尝试 dynamic_cast
来实现 getSingleton<T>
。但我想知道是否有更优雅的方法,不需要线性时间。
标准库提供了std::type_index
,可用于有效地将唯一值关联到每个类型。此类型旨在用作关联容器的键。 std::unordered_map<std::type_index, std::any>
可用于包含任何类型的集合。由于知道该映射中任何元素的原始类型需要知道原始类型(以构造正确的 std::type_index
键),因此应该始终可以成功 any_cast
值。
这是一个示例实现 (godbolt) :
#include <any>
#include <typeindex>
#include <unordered_map>
class O
{
public:
template<class T>
T& getSingleton()
{
// Get a unique key for the type `T`
const auto key = std::type_index(typeid(T));
// Check if the element already exists
// If it doesn't, construct it in place
// In either case, returns an iterator to the object
auto[iter, unused] = members.try_emplace(key, std::in_place_type<T>);
// Get a reference to the value using its original type
return std::any_cast<T&>(iter->second);
}
private:
std::unordered_map<std::type_index, std::any> members;
};
用法如下:
#include <iostream>
int main()
{
O o;
// Will value initialize an `int`
int & i = o.getSingleton<int>();
// `i` and `j` refer to the same object
int & j = o.getSingleton<int>();
std::cout << i << ' ' << j << '\n';
// Changing `i` changes `j`
i = 42;
std::cout << i << ' ' << j << '\n';
}
此解决方案对T
的唯一要求是它默认可构造和可破坏。
可以使用类似这样的东西:
#include <map> // for map
#include <memory> // for unique_ptr
#include <tuple> // for ignore, tie
class O {
private:
template <class T>
static void index(){};
using obj_ptr = std::unique_ptr<void, void (*)(void*) noexcept>;
std::map<void (*)(), obj_ptr> instances;
public:
template <class T>
T& getSingleton() {
auto it = instances.find(index<T>);
if (it == instances.end()) {
static constexpr auto deleter =
+[](void* t) noexcept { delete static_cast<T*>(t); };
std::tie(it, std::ignore) =
instances.emplace(std::pair{&index<T>, obj_ptr{new T, deleter}});
}
return *static_cast<T*>(it->second.get());
}
};