我有一个名为Request的模板化基类:
template <typename Input, typename Output>
class Request
{
public:
// Constructor
Request(const std::string& request) : request_(request) {}
// Operation
virtual Output select(const Input& arg) = 0;
protected:
// Attribute
const std::string request_;
};
我还创建了一个专门用于搜索模板的类,这只是一个例子,来自用户名的电话号码。
// Special request that get phone numbers from usernames
class PhoneRequest : public virtual Request<std::string, std::string>
{
public:
protected:
std::string username_;
};
然后我创建了两个搜索两个SQL表的类:
class NationalPhoneRequest : public virtual PhoneRequest
{
public:
// Real request that actually get data from the database
virtual std::string select(const std::string& username)
{
username_ = username;
return call_sql_national(request_, username_);
}
};
class InterNationalPhoneRequest : public virtual PhoneRequest
{
public:
// Real request that actually get data from the database
virtual std::string select(const std::string& username)
{
username_ = username;
return call_sql_international(request_, username_);
}
};
然后,当我想测试我的代码时,我创建了一个通用的Fake类:
template <typename Input, typename Output>
class Fake : public virtual Request<Input, Output>
{
public:
// Constructor
Fake(const std::string& request) : Request<Input, Output>(request) {}
// Fake Operation
virtual Output select(const Input& arg)
{
return data_[arg];
}
// Add fake data
void add_data(const Input& key, const Output& data)
{
data_[key] = data;
}
private:
// Contains fake data
std::map<Input, Output> data_;
};
最后,我创建了一个能够模拟InterNationalPhoneRequest和NationalPhoneRequest的类,因为这两个非常接近。那堂课也是假的。我使用关键字“using”来明确指出此类必须使用Fake基类方法。
class FakePhoneRequest : public NationalPhoneRequest, public InterNationalPhoneRequest, public Fake<std::string, std::string>
{
public:
using Fake<std::string, std::string>::select;
};
我还创建了另一个使用以前所有内容的类:
class Foo
{
public:
Foo(NationalPhoneRequest* nat, InterNationalPhoneRequest* internat)
: nat_(nat)
, internat_(internat)
{
}
std::string getNatPhoneNumber(const std::string& user)
{
return nat_->select(user);
}
std::string getInterPhoneNumber(const std::string& user)
{
return internat_->select(user);
}
private:
NationalPhoneRequest* nat_;
InterNationalPhoneRequest* internat_;
};
然而,g ++没有抱怨:
没有唯一的最终覆盖'输出请求:: select(const输入&amp;)[使用Input = std :: basic_string; 'FakePhoneRequest'类中的输出= std :: basic_string]' FakePhoneRequest:公共NationalPhoneRequest,公开 InterNationalPhoneRequest,public Fake
我有几个问题。 1)为什么会失败? 2)我脑子里有一个触发器告诉我,我做错了,可能是使用了三重继承。
我做得对吗?
答案 0 :(得分:2)
您的子类实际上继承了三个完全独立的“选择”函数,这些函数仍存在于子元素中,“using”只允许您使用“select”作为其中一个的简写名称。这样做就不会“合并”三个现有的选择函数。
基本上,继承三个具有相同名称的虚函数会给孩子三个不同的虚函数指针,每个指针选择一个“类型”(因为它们在不同的层次结构中)。
C ++不允许这样做,因为没有办法分别正确地定义所有三个选择函数的子版本,因为它们都必须被称为“select”,这会混淆编译器的地狱
如果三个选择函数采用不同的参数,那么可能没有问题。函数的“内部”名称由给定名称和一些定义所需参数的标记定义。因此,选择带有一个字符串的“select”是一个不同的“命名”函数来选择带两个字符串。只要编译器可以确定哪个函数是哪个函数,那么重用名称就没问题了。
话虽如此,代码有点臭。任何时候,如果你对继承相关的事情变得过于深入,那么几乎总会有更好的方法来做事。一个经验法则是尝试将继承只保持1级深,并使基类是抽象的(即不能自己实例化)。然后使用指向基类的指针将“插入”功能“插入”到不同的类中(它们本身可能是1-deep抽象类的子类)。
e.g。创建一个“PhoneDialler”类,它有一个指向PhoneRequest的指针。然后你有一个函数返回一个指向PhoneRequest类型之一的指针。这称为工厂方法。然后PhoneDialler只是请求一个PhoneRequest对象并使用它,并且根本不需要知道PhoneRequest的子类。这使得系统更容易使用新类型进行扩展。