三重继承,没有独特的最终覆盖

时间:2017-03-16 13:14:55

标签: c++ inheritance

我有一个名为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)我脑子里有一个触发器告诉我,我做错了,可能是使用了三重继承。

我做得对吗?

1 个答案:

答案 0 :(得分:2)

您的子类实际上继承了三个完全独立的“选择”函数,这些函数仍存在于子元素中,“using”只允许您使用“select”作为其中一个的简写名称。这样做就不会“合并”三个现有的选择函数。

基本上,继承三个具有相同名称的虚函数会给孩子三个不同的虚函数指针,每个指针选择一个“类型”(因为它们在不同的层次结构中)。

C ++不允许这样做,因为没有办法分别正确地定义所有三个选择函数的子版本,因为它们都必须被称为“select”,这会混淆编译器的地狱

如果三个选择函数采用不同的参数,那么可能没有问题。函数的“内部”名称由给定名称和一些定义所需参数的标记定义。因此,选择带有一个字符串的“select”是一个不同的“命名”函数来选择带两个字符串。只要编译器可以确定哪个函数是哪个函数,那么重用名称就没问题了。

话虽如此,代码有点臭。任何时候,如果你对继承相关的事情变得过于深入,那么几乎总会有更好的方法来做事。一个经验法则是尝试将继承只保持1级深,并使基类是抽象的(即不能自己实例化)。然后使用指向基类的指针将“插入”功能“插入”到不同的类中(它们本身可能是1-deep抽象类的子类)。

e.g。创建一个“PhoneDialler”类,它有一个指向PhoneRequest的指针。然后你有一个函数返回一个指向PhoneRequest类型之一的指针。这称为工厂方法。然后PhoneDialler只是请求一个PhoneRequest对象并使用它,并且根本不需要知道PhoneRequest的子类。这使得系统更容易使用新类型进行扩展。