【问题标题】:Get list entry of type T at compile-time [closed]在编译时获取类型 T 的列表条目 [关闭]
【发布时间】:2018-10-06 10:45:25
【问题描述】:

我有一个存储抽象类的一些对象的容器类。在某些部分,程序需要将这些对象作为实现类来获取。我写了一个通用的 get 函数,但它必须遍历所有存储的对象,这会花费很多时间。据我所知,容器只有一个类型的对象,我想在编译时解决这个问题,但不想手动为每种类型创建自己的成员。

目前我没有遇到“浪费”运行时的问题,但我想学习如何解决这个问题。

我目前的实现:

#include <iostream>
#include <list>

class Abstract
{
public:
    virtual void update() = 0;
};

class ImplA : public Abstract
{
public:
    void update() {std::cout << "implA" << std::endl;}

};

class ImplB : public Abstract
{
public:
    void update() {std::cout << "implB" << std::endl;}

};

class Container
{
public:
    Container(){
        content.push_back(new ImplA);
        content.push_back(new ImplB);
    }

    void update() {
        for (Abstract* obj : content)
            obj->update();
    }

    template<typename T> T* get() const {
        for (Abstract* obj : content) {
            if(dynamic_cast<T*>(obj) != nullptr)
                return dynamic_cast<T*>(obj);
        }
        return nullptr;
    }

private:
    std::list<Abstract*> content;
};

int main()
{
    Container* container = new Container();
    container->get<ImplA>()->update();
    container->get<ImplB>()->update();
    return 0;
}

我对如何解决这个问题的想法:

  • 为每种类型创建自己的成员并获取函数
  • 不要使用列表,而是使用映射并使用 typeinfo.name 作为键(但这是一个运行时解决方案,是个坏主意)
  • 实现类型列表(例如枚举类型 {implA, implB})并将其用作映射键。在这里,我必须为每种类型创建一个新条目。
  • 使用诸如编译时映射之类的东西,它可以使用类型作为键。

提前致谢!

【问题讨论】:

  • 也许你可以做一些CRTP诡计,以避免dynamic_casts并用static_casts替换它们,这将在编译时解决。
  • 我记得我们一直在使用 enum 和某种 X macros 解决类似的情况,框架实现相当复杂,团队中的新开发人员学习曲线非常陡峭正确实现新类型(声明类、添加新枚举值、添加宏调用以实现getType() 函数等)。我真的会考虑是否值得克服您面临的这种小的运行时开销。
  • @OP,是否总是所有注册都在同一个地方(Container 的构造函数在这里)?如果不是,那会使解决方案变得更加复杂,因为它们需要在链接时而不是编译时进行聚合。
  • @Quentin 是的,Container 的构造函数将始终创建所有对象。

标签: c++ templates compile-time


【解决方案1】:

如果你的对象总是插在一起,那么你的问题已经被std::tuple解决了:

#include <iostream>
#include <tuple>

template <class... Ts>
struct Container : std::tuple<Ts...> {
    using std::tuple<Ts...>::tuple;

    void update() {
        std::apply([](auto &... objects){ (objects.update(), ...); }, *this);
    }
};

namespace std {
    template <class... Ts>
    struct tuple_size<Container<Ts...>> : tuple_size<tuple<Ts...>> { };
}

template <class... Ts>
Container(Ts...) -> Container<Ts...>;

Container 通过隐式转换为std::tuplestd::tuple_size 的特化支持std::get

See it Live on Coliru(针对 C++17 之前的 GCC 进行了调整:/)

【讨论】:

  • 不错。我不知道 C++14 添加了带有类型参数的 get 的重载。为什么需要tuple_size的专业化?
  • @Cheersandhth.-Alf 因为std::get 在某些时候将其专门用于其参数,并且不考虑到基类的隐式转换——它必须完全匹配。
【解决方案2】:

至少有两种主要方法可以提供对不同类型对象的恒定时间访问:

  • 使用多重继承,向下转换为对象类型。
  • 使用由std::type_index 索引的哈希表(如std::unordered_set)。

提供编译时访问的多重继承方法示例:

#include <iostream>
#include <vector>
using namespace std;

class Abstract
{
public:
    virtual void update() = 0;
};

class Impl_a : public Abstract
{
public:
    void update() override { cout << "impl A" << endl; }

};

class Impl_b : public Abstract
{
public:
    void update() override { cout << "impl B" << endl; }

};

template< class Type >
struct Boxed_
{
    Type object;
};

class Container:
    public Boxed_<Impl_a>,
    public Boxed_<Impl_b>
{
    vector<Abstract*> m_items;

    Container( const Container& ) = delete;
    auto operator=( const Container& ) -> Container& = delete;

public:
    void update()       // Not an override of base class function
    {
        for( Abstract* obj : m_items ) { obj->update(); }
    }

    template< class Type >
    auto get()
        -> Type*
    { return &static_cast<Boxed_<Type>&>( *this ).object; }

    Container():
        m_items{{ get<Impl_a>(), get<Impl_b>() }}
    {}
};

auto main()
    -> int
{
    Container c;
#ifdef INDIVIDUAL_CALLS
    c.get<Impl_a>()->update();
    c.get<Impl_b>()->update();
#else
    c.update();
#endif
}

如果您希望容器作为const 持有者对非const 对象的引用,只需添加一个额外的间接级别。

这是软件工程的基本定理,David Wheeler:

计算机科学中的所有问题都可以通过另一个层次的间接来解决

【讨论】:

  • 这并不能像 OP 所要求的那样在编译时解决整个问题。
  • @πάνταῥεῖ:确实如此。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-11-13
  • 2013-08-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多