【问题标题】:Approach for setting dynamic constructor parameter inside template class在模板类中设置动态构造函数参数的方法
【发布时间】:2020-06-25 10:15:58
【问题描述】:

假设我们有一个基于另一个类 Tile 的模板类(我称之为 TileCreator )。 在某些情况下,Tile 类的构造函数需要参数进行初始化。 现在 TileCreator 类将构造许多 Tile 对象,所以我想将必要的参数作为成员变量存储在 TileCreator 中。

有什么好的方法吗?如何让我的模板代码知道这些参数的类型和数量?

我唯一的方法是使用指向它的 void* 指针,但是,我每次都需要手动分配和释放内存吗?

理想情况下它应该像这样工作:

template<class Tile> class TileCreator {
public:
    void CreateNew() {
        Tile tile(par);
    }
    void setPar(void* par) {
        this->par = par;
    }
private:
    void* par;
};

class Cake {
public:
    Cake(int sugar, bool chocolate) {
        // ...
    }
};

int main() {
    TileCreator<Cake> baker;
    // baker.setPar(over 9000, true); obviously doesnt work like this
    return 0;
}

我必须分配一个包含所有参数的结构并给出它的指针,然后还为每个可能的 Tile 添加构造函数以正确读取该结构并释放它。

我希望有一个更优雅的解决方案!

【问题讨论】:

  • 如果 tile creator 支持的类型集已知,那么您可以使用 std::variant

标签: c++ class oop templates initialization


【解决方案1】:

编辑了我对以下内容的回答:

这有一个缺点,您需要在Creator 中实例化一个模板对象,在通过调用复制构造函数创建其他对象时将使用该模板对象。您当然需要管理指针(可能使用智能指针)

template <typename Tile>
class Creator {
  private:
    Tile *tileTemplate;
  public:

    template <typename... Args>
    Creator(Args&& ... args) {
      tile = new Tile(std::forward<Args>(args)...); 
    }
    void createNew() {
      Tile tile = *tileTemplate;
    }
    ~Creator() {
      delete tileTemplate;   
    }
};

class A {
  public:
  A(int a, int b):a(a) {} 
};

class B {
  public:
  B(const std::string& a):a(a) {}
};

int main()
{

  Creator<A> ca(1,2);
  ca.createNew();

  Creator<B> cb("Hello");
  cb.createNew();

}

【讨论】:

  • 我觉得思路是保存一组参数,多次使用。在您的代码中,您立即创建 Tile 对象。
  • @Grandbrain 谢谢,你是对的。我相应地调整了我的答案
  • 谢谢!它确实有效:D 使用模板的想法实际上是如此简单,我什至不需要在 Creator 构造函数中转发参数,因为我需要更改参数几次,所以我可以只使用 setTemplate 函数放置一个模板...
【解决方案2】:

您可以使用std::tuple 存储参数,并使用std::apply 调用Tile 构造函数。

template <typename T, typename... Tn>
class Factory
{
private:
    std::tuple<Tn...> args;

public:
    template <typename... Args>
    explicit Factory(Args&&... args)
    : args(std::forward<Args>(args)...)
    {
    }

    T operator()() const {
        return std::apply([](Tn const&... args)
        {
            return T(args...);
        }, args);
    }
};

template <typename T, typename... Args>
auto make_factory(Args&&... args)
{
    return Factory<T, std::decay_t<Args>...>(std::forward<Args>(args)...);
}

这样使用:

int main()
{
    auto const make_tile = make_factory<Tile>(1, 2.0f, true);
    make_tile();
    make_tile();
}

您甚至可以将 Factory 类替换为 lambda 和 std::bind 以更轻松地实现。

template <typename T, typename... Args>
auto make_factory(Args&&... args)
{
    return std::bind([](auto&&... args)
    {
        return T(std::forward<decltype(args)>(args)...);
    }, std::forward<Args>(args)...);
}

在 C++20 中,您可以使用 std::bind_front,它还允许您在创建时传递其他参数。

template <typename T, typename... Args>
auto make_factory(Args&&... args)
{
    return std::bind_front([](auto&&... args)
    {
        return T(std::forward<decltype(args)>(args)...);
    }, std::forward<Args>(args)...);
}
int main()
{
    auto const make_tile = make_factory<Tile>(1, 2.0f);
    make_tile();
    make_tile(true);
    make_tile(false, "hello");
}

【讨论】:

  • 哇,谢谢你的回答。我以为我已经熟悉了大多数 c++,但是可变参数模板和参数包(所有带有“...”的东西)现在实际上非常混乱,不知道从哪里开始完全理解发生了什么,但我猜我一整天都在处理你的代码。谢谢!
  • 所以在尝试将您的第一个解决方案实施到我的解决方案后,我仍然收到错误 C2664: 'std::tuple::tuple(const std::tuple &) noexcept': cannot将参数 1 从 '_Ty' 转换为 'std::allocator_arg_t' - 无法真正了解它的含义。在将您的代码复制到一个空项目之后,我得到了同样的错误......我用 c++17 和 c++20 尝试过,两者都不会编译。遗憾的是,我无法使用 lambda 解决方案,因为我的实际“工厂”更大更复杂,实际上它是一个管理 std::map> 数据类型的容器类。也许我忘了#include 一些东西?
  • @Kaisky 恐怕我不太清楚会发生什么。看起来它与任何 tuple 构造函数都不匹配。也许您传递的参数不兼容。
猜你喜欢
  • 2023-02-24
  • 1970-01-01
  • 2012-09-14
  • 2017-10-31
  • 1970-01-01
  • 2012-12-10
  • 2018-05-18
  • 2016-09-02
  • 2015-05-05
相关资源
最近更新 更多