自动 x = make_x(..) 和这个

auto x = make_x(...) and this

本文关键字:make 自动      更新时间:2023-10-16

我有一个类模板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 << "func1n";
}
int main()
{
struct Func2
{
void operator ()() const {
std::cout << "func2n";
}
};
//
// 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 << "func3n"; });
//
// 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,...

你的基本问题是你未能实现 3 规则。 如果你的析构函数需要非平凡的行为(如果你在构造函数中注册自己,就是这种情况),你必须实现或阻止赋值和复制构造(和/或移动赋值和移动构造)。

在这种情况下,我们可以实现一个move-construct和blockmove-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,当moved 时,它会更新Map~S,我假设,从Map取消注册 - 检测你的name是否为空(并在构造时断言你获得一个非空名称),如果是,不要取消注册(因为你已经从moved)。

现在move构造和省略构造具有相同的语义。 RVO 故障会导致一些效率低下,但没有逻辑故障。 另外,您的类型现在move能力,这往往非常有用。

如果需要维护对象标识,使用 可以使用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)) };
}

现在,将指针从一个位置移动到另一个位置不会移动对象;保留在地图中的指针将保持有效。

我对改进代码的建议:
1)摆脱构造函数中的副作用。仅在工厂方法中创建对象(makeS在代码中;您可以使其成为S的静态成员函数),并在该方法中注册S对象。要以不同的方式禁用对象创建,请将构造函数设为私有。

2)禁用S对象复制/移动,并将对象处理为例如shared_ptr/unique_ptr<S>。禁用复制/移动时,可以避免地图包含指向无效对象的指针时出现问题,现在您不需要依赖 RVO。

3)使用std::function<void()>代替T Func;。在这种情况下,您的类不需要是模板类(或者它将具有较少的模板参数)。这将简化您的代码。