我有一个名为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 ++中不存在这样的事情,因此我的问题。)
对于这类问题,是否有更清洁,惯用的解决方案?
答案 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)
您可以选择以下选项:
如果您不需要populate_container()作为DBDriver的成员并且可以扩展(或定义)容器接口,那么只需实现
ContainerTypeOne::populate(DBCursor& cursor);
让populate_container()成为朋友(如果您需要访问DBDriver的私有成员):
template <typename CONT_T> friend void execute_query(const std::string& query, CONT_T& container);
使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);
}