我有一个类模板S<T>
,因为模板参数有时候是硬写的,所以我也有一个辅助函数makeS(...)
来推导出模板参数。
现在的问题是S
的构造函数有一个“副作用”:它将自己添加到map
,然后将其用于迭代S
的所有实例。但这有效地使S<T> s{...};
与auto s = makeS(...);
非常不同(如果不使用RVO)。
这是一些代码,希望能够展示我正在尝试做的事情。 (注意:在实际程序中,S
包含多个模板参数,所有内容都将在makeS
中推断出来)
#include <cassert>
#include <iostream>
#include <map>
#include <string>
#include <utility>
using namespace std;
struct Base
{
virtual ~Base() {}
virtual void f() const = 0;
};
static map<string, Base*> Map;
template <typename T>
struct S : Base
{
T Func;
Base* This;
S(string const& name, T func) : Func(std::move(func)), This(this)
{
//
// Automatically add this struct to Map...
//
Map.insert({name, this});
}
virtual void f() const override { Func(); }
};
template <typename T>
S<T> makeS(std::string const& name, T func)
{
return S<T>(name, std::move(func));
}
void func1()
{
std::cout << "func1\n";
}
int main()
{
struct Func2
{
void operator ()() const {
std::cout << "func2\n";
}
};
//
// This is not possible:
//
// S< ??? > s("s", [](){});
//
// This would be ok:
//
// auto F = [](){};
// S<decltype(F)> s("s", F);
//
auto s1 = makeS("s1", func1);
auto s2 = makeS("s2", Func2());
auto s3 = makeS("s3", [](){ std::cout << "func3\n"; });
//
// Manually adding s1,... to the Map is ok, but that's what
// I want to avoid...
//
// Map.insert({"s1", &s1});
// ...
//
assert(&s1 == s1.This);
assert(&s2 == s2.This);
assert(&s3 == s3.This);
for (auto&& I : Map)
{
I.second->f();
}
}
根据我的理解,如果在auto s1 = makeS(...)
等中使用RVO,地图将只包含有效指针,但这不能保证。
有没有办法推断出模板参数,同时又无需手动注册s1,...
?
答案 0 :(得分:1)
您的基本问题是您未能实现3的规则。如果您的析构函数需要非平凡的行为(如果您在构造函数中注册自己,就是这种情况),您必须实现或阻止赋值和复制构造(和/或move-assign和move-construct)。
在这种情况下,我们可以实现move
- 构造并阻止move
- assign,并且隐式阻止复制构造和复制赋值。
首先,将name
添加到S
。然后实现move
构造函数。
template <typename T>
struct S : Base
{
std::string Name;
T Func;
Base* This; // ?? why ?? this looks both dangerous and useless at the same time!
S( S&& s ): Name(std::move(s.Name)), Func(std::move(s.Func)), This(this) {
s.clear(); // technically `move` need not clear.
map[Name] = this; // overwrite
}
S& operator=(S&& s) = delete; // or implement it
现在您的对象已move
能够,move
时它会更新Map
。我认为~S
应该从Map
取消注册 - 检测您的name
是否为空(并在构造时声明您获得非空名称),如果不是取消注册(因为你已经move
来自哪里)。
现在move
- construct和elided-construct具有相同的语义。 RVO故障导致一些低效率,但没有逻辑故障。此外,您的类型现在move
能够使用,这往往非常有用。
答案 1 :(得分:0)
如果您需要维护对象标识,请使用std::unique_ptr
:
template <typename T>
std::unique_ptr<S<T>> makeS(std::string const& name, T func)
{
return { new S<T>(name, std::move(func)) };
}
现在将指针从一个地方移动到另一个地方不会移动对象;保存在地图中的指针将保持有效。
答案 2 :(得分:0)
我对改进代码的建议:
1)摆脱构造函数中的副作用。仅在工厂方法中创建对象(在代码中为makeS
;您可以使其成为S
的静态成员函数)并在该方法中注册S
个对象。要以不同方式禁用对象创建,请将构造函数设置为私有。
2)禁用S
个对象复制/移动并处理对象,例如仅shared_ptr
/ unique_ptr<S>
。当您禁用复制/移动时,当地图包含指向无效对象的指针时,您可以避免此问题,现在您不需要依赖RVO。
3)使用std::function<void()>
代替T Func;
。在这种情况下,您的类不需要是模板类(或者它将具有更少的模板参数)。这将简化您的代码。