【问题标题】:C++ inherited operator '<<' on templated class模板类上的 C++ 继承运算符“<<”
【发布时间】:2020-03-27 11:35:26
【问题描述】:

我有以下 C++ 代码,它演示了我的问题。 我的目标是覆盖继承类中的流运算符,以便允许我根据对象类型打印特定的流:

#include <iostream>
#include <unordered_set>

using namespace std;

template <typename T>
class Base {
    public:
        Base(){}
        Base(T n): value_(n){}

        friend inline ostream &operator<<(ostream &os, const Base &b) {
            b.to_str(os);
            return os;
        }

    protected:

        T value_;

        // All object should implement this function
        virtual void to_str(ostream& os) const {
            os << value_;
        }
};

template <typename T>
class Child: public Base<T> {
    public:
        Child(T n): Base<T>(n){}

    protected:
        void to_str(ostream& os) const override {
         os << "{";
            for (auto v = this->value_.begin(); v != this->value_.end(); v++) {
                if(v != this->value_.begin())
                    os << ",";
                os << (*v);
            }
            os << "}";
        }
};

int main()
{
    Base<string> b("base");
    Child<unordered_set<string>> c({"child"});
    cout << "b: " << b << endl;
    cout << "c: " << c << endl;

    return 0;
}

目前代码未编译:

main.cpp:31:16: error: no match for ‘operator<<’ (operand types are
‘std::ostream {aka std::basic_ostream}’ and ‘const std::unordered_set
>’)

似乎编译器使用了来自 base 的虚方法 to_str(),而不是 Child 类的覆盖方法。 如果我注释了基础 to_str() 函数的主体,那么它会编译并打印 unordered_set Child 类的正确结果,但是它不会在基础实现中打印任何内容。 所以覆盖是有效的,但是为什么当 base to_str() 有一个主体时它不编译?

如何强制编译器使用派生的一个(子级)?

问候

【问题讨论】:

  • 您没有正确复制错误消息。缺少一些碎片。
  • 它将始终使用to_str()Child 版本。但是编译器仍然希望为std::unordered_set 生成函数Base::to_string(),这是合理的,因为它仍然可以被调用(并且在构建Base 期间需要该地址才能正确构建V-Table(假设实现使用V -Tables)。一个解决方案是在 Base 中将to_str() 设为纯虚拟。然后必须为 Child 派生实现和一个新类 StandardStremable。
  • @MartinYork 感谢您的提示,正在考虑您提出的解决方案。我将发布修改后的代码。

标签: c++ inheritance operator-overloading operator-keyword


【解决方案1】:

这就是我最终使用“纯虚拟解决方案”实现的, 用一些更基本的类型测试:

#include <iostream>
#include <unordered_set>

using namespace std;

template <typename T>
class Base_ {

    public:
        Base_(){}
        Base_(T n): value_(n){}

        friend inline ostream &operator<<(ostream &os, const Base_ &b) {
            b.to_str(os);
            return os;
        }

    protected:
        T value_;

        // All object should implement this function
        virtual void to_str(ostream& os) const = 0;
};

template <typename T>
class Base: public Base_<T> {
    public:
        Base(){}
        Base(T n): Base_<T>(n){}

    protected:

        // All object should implement this function
        void to_str(ostream& os) const override {
            os << this->value_;
        }
};

template <typename T>
class Child: public Base_<T> {
    public:
        Child(T n): Base_<T>(n){}

    protected:
        void to_str(ostream& os) const override {
         os << "{";
            for (auto v = this->value_.begin(); v != this->value_.end(); v++) {
                if(v != this->value_.begin())
                    os << ",";
                os << (*v);
            }
            os << "}";
        }
};

template <typename T>
class Boolean: public Base_<T> {
    public:
        Boolean(T n): Base_<T>(n){}

    protected:
        void to_str(ostream& os) const override {
         os << (this->value_ ? "true" : "false");
        }
};

int main()
{
    Base<string> s("string");
    Base<int> i(42);
    Boolean<bool> b(true);
    Child<unordered_set<string>> u({"child1", "child2"});
    cout << "s: " << s << endl;
    cout << "i: " << i << endl;
    cout << "b: " << b << endl;
    cout << "u: " << u << endl;

    return 0;
}

结果:

s: 字符串
我:42
b: 真的
你:{child2,child1}

【讨论】:

    【解决方案2】:

    虚拟调用确实发生了。您可以通过将代码更改为

    template <typename T>
    class Base {
        public:
            Base(){}
            Base(T n): value_(n){}
    
            friend inline ostream &operator<<(ostream &os, const Base &b) {
                b.to_str(os);
                return os;
            }
    
        protected:
    
            T value_;
    
            virtual void to_str(ostream& os) const = 0;
    };
    
    template <typename T>
    class Child: public Base<T> {
        public:
            Child(T n): Base<T>(n){}
    
        protected:
            void to_str(ostream& os) const override {
             os << "{";
                for (auto v = this->value_.begin(); v != this->value_.end(); v++) {
                    if(v != this->value_.begin())
                        os << ",";
                    os << (*v);
                }
                os << "}";
            }
    };
    
    int main()
    {
        Child<unordered_set<string>> c({"child"});
        cout << "c: " << c << endl;
        return 0;
    }
    

    实际发生的情况是,在Base 中,编译器将被淘汰

    Base<unordered_set<string>>::to_str
    

    并且该函数无效,因为未定义 os &lt;&lt; value_。如您所见,您需要将其设为纯虚拟,或者放入无论value_ 是什么都能编译的存根。

    【讨论】:

    • 谢谢你的回答,我会贴一个纯虚拟实现的解决方案
    【解决方案3】:

    基础 to_str 即使从未运行过也会被编译。

    所以

        virtual void to_str(ostream& os) const {
            os << value_;
        }
    

    编译失败。

    当您使用 vtable 创建类型时,即使未调用其条目(嗯,我相信标准说“可以”),也会填充其条目。这与“普通”模板类不同,其中未使用的方法“跳过”了它们的主体。

    您可能需要 CRTP 来实现编译时多态性。

    template <class T, class D_in=void>
    class Base {
        using D=std::conditional_t< std::is_same<D_in,void>{}, Base, D_in >;
        public:
            Base(){}
            Base(T n): value_(n){}
    
            friend inline ostream &operator<<(ostream &os, const Base &b) {
                static_cast<D const&>(b).to_str(os);
                return os;
            }
    
        protected:
    
            T value_;
    
            // All object should implement this function
            void to_str(ostream& os) const {
                os << value_;
            }
    };
    
    template <typename T>
    class Child: public Base<T, Child<T>> {
        friend class Base<T, Child<T>>;
        public:
            Child(T n): Base<T>(n){}
    
        protected:
            void to_str(ostream& os) const {
             os << "{";
                for (auto v = this->value_.begin(); v != this->value_.end(); v++) {
                    if(v != this->value_.begin())
                        os << ",";
                    os << (*v);
                }
                os << "}";
            }
    };
    

    或类似的。

    【讨论】:

    • 哇哦很高兴知道这个存在!!!我将实施纯虚拟解决方案,因为它对我来说似乎更清楚。还是谢谢你
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-04-24
    • 2020-10-14
    • 1970-01-01
    • 2012-10-05
    • 1970-01-01
    相关资源
    最近更新 更多