访问派生类'基本模板函数中的成员函数

时间:2017-01-25 05:30:25

标签: c++ templates inheritance

我有一个名为DBDriver的类,它处理与数据库中给定表的通信。它的公共入口点是一个名为execute_query()的函数模板,它执行SELECT查询。在调用此函数时,将执行一些数据库逻辑,然后使用结果填充提供的容器(模板类型)。这看起来如下所示:

class DBDriver {

    ...

    template <typename CONT_T>
    void execute_query(const std::string& query, CONT_T& container);

    ...

};

template <typename CONT_T>
void DBDriver::execute_query(const std::string& query, CONT_T& container) {
    DBCursor& cursor = ...  // some database logic here
    populate_container(container, cursor);
}

当然,上述内容无法编译,因为populate_container()中未定义DBDriver

DBDriver应该是纯虚拟的,并且从中派生出几个类(每个数据库表对应一个)。每个派生类都将定义自己的populate_container()重载,每个相关容器类型一个。这看起来如下所示:

class SampleTableDBDriver : public DBDriver {

    // ...

    populate_container(const ContainerTypeOne& container, DBCursor& cursor);
    populate_container(const ContainerTypeTwo& container, DBCursor& cursor);

    // ...

};

我最初的尝试是不成功的,因为我需要在DBDriver中定义一个虚拟功能模板,作为派生类的入口点&#39; populate_container()超载。 (当然,在C ++中不存在这样的事情,因此我的问题。)

对于这类问题,是否有更清洁,惯用的解决方案?

4 个答案:

答案 0 :(得分:1)

execute_query是模板函数的原因是您需要一个通用容器。如果为容器定义Interface该怎么办?

class IContainer
{};

模板功能不能是虚拟的。因此,您可以使用Template Method Design Pattern

class DBDriver 
{
    public:
    void execute_query(const std::string& query, IContainer **_ppContainer);
    {
        DBCursor& cursor = ...  // some database logic here
        populate_container(_ppContainer, cursor);
    }

    virtual void populate_container(IContainer **_ppContainer, DBCursor &_dbCursor) = 0;
};

让每个派生类实现populate_container并提供自定义Container

class SampleTableDBDriver : public DBDriver 
{
    public:
        class ContainerTypeOne : public IContainer
        {};

        void populate_container(IContainer **_ppContainer, DBCursor &_dbCursor) 
        {
              ContainerTypeOne *pContainer = new ContainerTypeOne();
              //....
              (*_ppContainer) = pContainer;
        }
};


SampleTableDBDriver  oSampleDriver;
IContainer *pContainer = NULL;
std::string szQuery = // some query ;
oSampleDriver.execute_query(szQuery, &pContainer);
if(pContainer != NULL)
{
    SampleTableDBDriver::ContainerTypeOne  *pSampleDriverContainer = 
        dynamic_cast<SampleTableDBDriver::ContainerTypeOne*>(pContainer); 

    //use pSampleDriverContainer 
}

修改:支持多个容器。

在原始设计中,populate_container似乎在派生类中重载。在这种情况下,您仍然可以在调用execute_query时从外部传递完全容器。 使用此Template Method设计可以完成同样的事情。然后,您需要解密populate_container函数内的容器类型,如下所示:

新签名:int populate_container(IContainer *_pContainer, DBCursor &_dbCursor)

int populate_container(IContainer *_pContainer, DBCursor &_dbCursor) 
{
      if(dynamic_cast<ContainerTypeOne *>(_pContainer) != NULL)
      {
          ContainerTypeOne *pContainerOne = _pContainer;
          //populate the result by using pContainerOne
          return 1;   
      }

      if(dynamic_cast<ContainerTypeTwo *>(_pContainer) != NULL)
      {
          ContainerTypeOne *pContainerTwo = _pContainer;
          //populate the result by using pContainerTwo
          return 1;   
      }

      //no, I do not support the container you passed.
      return 0;
}

SampleTableDBDriver  oSampleDriver;
SampleTableDBDriver::ContainerTypeOne oSampleContainerTypeOne;
std::string szQuery = // some query ;
if(oSampleDriver.execute_query(szQuery, &oSampleContainerTypeOne) != 0)
{
    //use oSampleContainerTypeOne;
}

答案 1 :(得分:0)

您可以选择以下选项:

  1. 如果您不需要populate_container()作为DBDriver的成员并且可以扩展(或定义)容器接口,那么只需实现

    ContainerTypeOne::populate(DBCursor& cursor);
    
  2. 让populate_container()成为朋友(如果您需要访问DBDriver的私有成员):

    template <typename CONT_T> friend void execute_query(const std::string& query, CONT_T& container);
    
  3. 使populate_container()成为非成员模板函数(如果您不需要访问DBDriver的私有成员)

答案 2 :(得分:0)

这样做的一种方法是采用分离关注原则。

查询数据库会转到自己的类层次结构,并且填充容器会转到其自己的单独的层次结构中。这两个层次结构彼此之间一无所知,例如DBDriverThirteen对ContainerFortyTwo一无所知。这两个层次结构只触及它们的根,即DBDriver(以及它的每个派生类)都知道IContainerPopulator,但没有任何关于任何特定容器的信息。

您可能拥有为每种容器类型构建特定ContainerPopulator的模板。为简单起见,您只需要支持实现push_back的字符串的标准容器。

struct IContainerPopulator {
    virtual void push_back(const std::string&) = 0;
};

template <class CONT_T>
struct ContainerPopulator : IContainerPopulator {
    StandardContainerPopulator (CONT_T& cont) : cont(cont) {}
    void push_back(const std::string& s) override { cont.push_back(s); }
  private:
    CONT_T& cont;
};

现在你可以这样做

template <typename CONT_T>
void execute_query(const std::string& query, CONT_T& container) {
   execute_query_adapted(query, ContainerPopulator<CONT_T>(container));
}

// no template!
virtual void execute_query_adapted(const std::string&,
                                   IContainerPopulator&&) = 0;

到目前为止,您可能已经认识到IContainerPopulator只不过是一个专门的穷人的功能活页夹。如果我们在语言及其标准库中得到很好的支持,为什么要写另一个呢?如果您只需要支持push_back,则可以选择执行此操作:

template <typename CONT_T>
void execute_query(const std::string& query, CONT_T& container) {
   execute_query_adapted2(query, 
                          [&](const std::string& s){container.push_back(s);});
}

// no template
virtual void execute_query_adapted2(const std::string&,
                                    std::function<void(const std::string&)>) = 0;

现在,如果您需要的不仅仅是push_back(或任何固定的功能集)和/或超过std::string(或任何固定的类型集),事情可能变得非常毛茸茸。关于populate_query的潜在实施,需要更多信息。

答案 3 :(得分:0)

只要您不介意使用RTTI(或Boost.TypeIndex,它不需要RTTI),您可以使用type erasure和{{1}得到你想要的东西。它有点脏,但是它可以完成这项任务。

void*

然后像这样使用:

#include <functional>
#include <typeindex>
#include <unordered_map>

class driver {
public:
    template <typename Container, typename Populator>
    void register_populator(Populator populator) {
        populators[type_index<Container>()] = [populator](void* v) {
            Container& container = *static_cast<Container*>(v);
            populator(container);
        };
    }

    template <typename Container>
    void execute(Container& container) {
        auto it = populators.find(type_index<Container>());
        if (it != populators.end()) {
            it->second(&container);
        }
    }

private:
    template <typename T>
    static std::type_index type_index() {
        return std::type_index(typeid(std::remove_cv_t<T>));
    }

    std::unordered_map<std::type_index, std::function<void (void*)>> populators;
};

输出:

#include <vector>
#include <iostream>

int main() {
    driver d;

    d.register_populator<std::vector<int>>([](std::vector<int>&) {
        std::cout << "Populate vector<int>\n";
    });

    d.register_populator<std::vector<float>>([](std::vector<float>&) {
        std::cout << "Populate vector<float>\n";
    });

    std::vector<int> int_vector;
    std::vector<float> float_vector;

    d.execute(int_vector);
    d.execute(float_vector);
}