【问题标题】:"Inheriting" a class with variadic templated function“继承”具有可变参数模板函数的类
【发布时间】:2012-05-12 08:00:21
【问题描述】:

我想写一个可以操作不同类型数据库(例如sqlite、postgres等)的数据库包装器,这样用户正在编写的代码无论他们实际使用什么数据库都不会改变。

在我看来,这需要一个抽象基类,例如:

class database {
public:
    virtual bool query(const std::string &q) = 0;
    // Other stuff
};

class sqlite : public database {
public:
    bool query(const std::string &q) {
        // Implementation
    }
};

这看起来不错,但我使用可变参数模板来转义查询中的参数(我真的很喜欢这个想法,所以我想坚持下去!),所以不幸的是我的基类看起来像:

class database {
public:
    template <typename... Args>
    bool query(const std::string &q, const Args &... args) {
        // Implementation
    }
};

然而,这阻碍了创建抽象类,因为模板函数不能是虚拟的。到目前为止我唯一想到的就是这个结构

template <class DatabaseType>
class database {
public:
    template <typename... Args>
    bool query(const std::string &q, const Args &... args) {
        return database_.query(q, args...);
    }
private:
    DatabaseType database_;
};

虽然这似乎可行,但所有包装器都只调用同名的database_ 函数,但对我来说它看起来不是一个很好的样式。这是这里选择的设计模式,还是有更简洁或更惯用的方式?

【问题讨论】:

    标签: c++ templates inheritance c++11 variadic-templates


    【解决方案1】:

    您可以根据query 函数的参数构建一个查询对象,并将其传递给一个虚拟的query_impl 函数。

    【讨论】:

    • 我一直在思考这个问题,但我还没有弄清楚它应该如何工作。如果我没看错,我仍然会有一个模板化对象 (query),因此我将无法执行 virtual bool query_impl(const query &amp;q) = 0; 之类的操作,因为我无法在没有参数列表的情况下使用该对象。你介意举例说明你的意思吗?
    • @nijansen:问题是您可以使用模板提供一个简单易用的界面,然后构建查询对象。考虑一种简单的方法,您可以只传递查询字符串和std::vector&lt;boost::any&gt;。在用户代码中使用参数创建该结构可能很麻烦,因此他们只使用可变参数模板函数,该函数又将元素存储在容器中并将它们传递给具有固定签名的虚函数:void query_impl( std::string, std::vector&lt;boost::any&gt; )
    • ...我实际上不会使用boost::any,而是boost::variant&lt;&gt;,因为它更明确一点,因为你不接受任何东西,只说intdouble、@ 987654331@... 但是思路是一样的,使用带有完整类型信息的模板来执行type-erasure(在这种情况下也是argument-list-length擦除)并将其转发给单个虚拟功能,然后可以为每个特定的数据库实现该功能。 (注意,我似乎是仅有的两个认为这是一个解决方案的人之一,并且 CRTP 方法无用
    • 感谢您为我解决这个问题。实际上,我看不到 CRTP 也不会提供任何好处,所以我会这样做。
    • @nijansen:您可能想在我的回答主题中查看我对 David 的回复,了解为什么在这种情况下我更喜欢 CRTP。
    【解决方案2】:

    [...] 有没有更简洁、更惯用的方式

    您可能需要查找 CRTP 以使用模板实现多态行为。

    template <class concrete_db> 
    struct abstract_db { 
        template <typename... Args>
        void query(std::string const& q, Args const&... args) { 
            static_cast<concrete_db *>(this)->query(q, args...);
        }
    };
    
    struct postgres_db : abstract_db<postgres_db> {
        template <typename... Args>
        void query(std::string const& q, Args const&... args) { 
             // do something
        }
    };
    

    【讨论】:

    • 这究竟为您提供了什么? abstract_db&lt;postgres_db&gt;abstract_db&lt;mysql_db&gt; 无关,这意味着abstract_db 不能真正多态地 使用,不是吗?也就是说,如果您持有对abstract_db&lt;postgres_db&gt; 的引用并调用query 方法,它将正确地分派到postgres_db,但您不是将一个特定类型postgres_db 换成另一个abstract_db&lt;postgres_db&gt;?我错过了什么?
    • @David : 任何在abstract_db&lt;&gt; 上的内容或功能都必须转换为模板——用动态多态性换取静态多态性。
    • @ildjarn:如果这是对我评论的回答,那么问题就来了,你为什么需要使用 CRTP?如果函数是模板,它们可以直接使用 postgres_db为什么需要使用 abstract_db&lt;postgres_db&gt; 添加额外级别的复杂性? 请注意,我了解 CRTP,但我不认为这个答案是有道理的:它添加了更多代码,一个额外的基础,并且与不使用它相比并没有提供任何改进。绝对我不赞成:)
    • @DavidRodríguez-dribeas:OP 已经有了他的草稿版本。注意报价。此外,查询可能不是abstract_db 中唯一的功能!我没有看到额外的复杂性——即使使用动态多态性,抽象基类仍然存在。进一步使用空基地优化,您甚至不会注意到基地在那里。 CRTP 将需要一个static 函数调用,而不是virtual,并且可能会更快。使用 CRTP 版本进行检查/策略也更容易,所以如果可以选择我会坚持使用 CRTP。
    猜你喜欢
    • 2019-04-25
    • 2012-03-13
    • 1970-01-01
    • 2015-11-10
    • 1970-01-01
    • 1970-01-01
    • 2014-05-15
    • 1970-01-01
    • 2019-12-14
    相关资源
    最近更新 更多