草庐IT

c++ - c++-通过抽象模板基类接口(interface)指针访问派生类方法,而接口(interface)中没有显式类型

coder 2024-02-25 原文

这是我的第一篇文章。我花了数小时检查问题的解决方案,在SO上逐个链接地搜索链接,但没有一个描述我的问题的确切信息(我能得到的最接近的是thisthis)。所以,让我们开始工作吧!

说明:我必须实现一组专门的类,每个类都可以存储其类型的链接列表。另外(棘手的部分),我必须实现一个集合管理器,以向集合中添加更多专业类不会影响其代码的方式。

让我解释一下我到目前为止所拥有的。

class IList {
    public:
    virtual IList& operator+( IList&) = 0;
    virtual void print() = 0;
    virtual int g_Size() const = 0;
//perfect till here

    virtual void Push(const int&) = 0;//needs to be upgraded
    virtual const int& operator[](int index) = 0;//needs to be upgraded
};

template<class T>
class Queue: public IList{
    //internal stuff
public:
    Queue();
    int g_Size() const;

    void print();

    void Push(const T& cv);

    const T& operator[](int index);

    ~Queue();
};//all implementation of Queue<T> is implemented and working, but removed for simplicity

class CIntList : public Queue<int>{
    //stuff here, specialized on int
};

class C_Manager{
    IList * classes[3];//notice the polymorphism, managing the class collection using a pointer to the common(base) interface
public:
    void testing()
    {
        for (int i = 0; i < 3; i++)
            classes[i] = new CIntList(i);
        classes[0]->Push(1); classes[0]->Push(2); classes[1]->Push(1121); classes[2]->Push(12);
        classes[0]->print();
        classes[2]->print();
        int a = classes[0]->operator[](1);
        classes[1]->Push(a + a);
    } //working fine
};

好的,所以您可能会问是什么问题?

我不想为我所有的类(class)专业重新声明Pushoperator[](或使用模板作为参数的任何其他函数)。更确切地说,如果我想添加,例如,
class CFloatList: public Queue<float>
{
      //float stuff goes here
};

我还必须将IList修改为
class IList {
        public:
        virtual IList& operator+( IList&) = 0;
        virtual void print() = 0;
        virtual int g_Size() const = 0;
    //perfect till here

        virtual void Push(const int&) = 0;//used for int
        virtual const int& operator[](int index) = 0;//used for int

    //NEW DECLARATION FOR FLOAT
        virtual void Push(const float&) = 0;//used for float
        virtual const float& operator[](int index) = 0;//used for float

    };

如何避免这些重新声明?我需要某种“虚拟功能模板”,但是C++不支持。

我的方法错了吗?

抱歉,没有突出显示c++语法,这是我的第一篇文章,我只设法在代码块中设置了格式。 谢谢您的宝贵时间!

编辑#1
更好的解决方案(如jaggedSpire所建议-非常感谢)

我已将IList修改为
class IList {
    public:
    virtual IList& operator+( IList&) = 0;
    virtual void afis() = 0;
    virtual int g_Size() const = 0;


    //templates
    template<typename T>
    void Push(const T& arg) //WORKS PERFECTLY
    {
        Queue<T>* cast = dynamic_cast<Queue<T>*>(this);
        cast->Push(arg);
    }
    template<typename T>
    const T& operator[](int index) //error
    {
        Queue<T>* cast = dynamic_cast<Queue<T>*>(this);
        return cast->operator[](index);
    }
};

void C_Manager::testing()
class C_Manager{
    public:
        void testing()
        {
            IList * a = new CIntList(1);
            a->Push(200);//WORKS PERFECTLY
            int c = a->operator[](0); //ERROR
        }
    };

并产生这些错误
Error   C2783   'const T &IList::operator [](int)': could not deduce template argument for 'T'

Error   C2672   'IList::operator []': no matching overloaded function found

intellisense: no instance of function template "IList::operator[]" matches the argument list

基本上,它会提示每个可能的模板化函数,都具有 T相关返回类型。如何解决这个问题,使我的经理真正多态?

最佳答案

首先,让我们回顾一下您的要求:

  • 具有非模板的多态基类IList
  • 有一个类模板,Queue<T>从基类继承来实现它。
  • 可以专门化Queue<T>,但是您想要
  • 假设您要从void返回push,并从const T&返回operator[],那么您想表示一个异常。
  • 将特定类型的参数传递给基类IList,其结果取决于Queue<T>的基础类型是否与给定参数的类型相匹配。

  • 最后一点是关键:您正在尝试根据调用者的运行时类型和参数的静态类型来选择函数的行为。但是,在运行时确定哪种类型实际上与实现的T中的Queue<T>相匹配。

    基于两个对象的运行时类型的运行时行为确定(因为该参数在运行时以及编译时都是已知的)是多方法的用途。 C++没有 native 多方法支持,但可以与dynamic_cast结合使用

    我通过this answer发现了与您当前问题的相似之处,它提供了一个很棒的链接数组,以提供有关在C++中实现完整的多方法功能(以及这些方法的实现)的更多详细信息。

    现在,在C++中对多方法进行蛮力/天真的实现将需要从实现类型列表中测试每种可能的实现类型的参数。您也已表明自己不需要,但不必担心:您将不需要。这是因为我们只想测试一种情况,而不是典型的多方法情况所需要的许多情况。当我们可以方便地使用该信息来查找我们感兴趣的唯一目标类型的类型时,我们将在编译时添加该参数的类型。

    对于提供的T类型,我们要测试要分派(dispatch)到的类型是否真的是Queue<T>

    为此,我们将使用更简单的多方法实现中使用的相同测试:dynamic_cast。具体来说,我们将使用提供的参数类型作为所需模板参数的源,将this指针转换为要测试的类型。

    警告:这意味着没有显式模板参数就不会发生类型之间的隐式转换。如果您将字符串文字传递给std::string容器,而未明确指定要使用std::string容器,则它将查找一个容器,该容器容纳字符串文字长度的字符数组,并且不检测任何字符数组。毕竟它们是不同的类型。

    话虽如此,让我们开始编写代码。对于由各种Parent实现的接口(interface)Child<T>,您可以使用它从只能通过T接口(interface)访问的Child<T>中获取Parent特定的行为:
    class Parent{
        public:
        template <typename T>
        void foo(const T& t);
    
        virtual ~Parent(){}
    };
    
    template <typename T>
    class Child : public Parent{
    
        public:
        void foo(const T& t);
    };
    
    // must be after the definition of the Child template, 
    // because dynamic_cast requires a complete type to target
    template <typename T>
    void Parent::foo(const T& t){
        // throws on bad conversion like we want
        auto castThis = dynamic_cast<Child<T>&>(*this); 
        // if execution reaches this point, this is a Child<T>
        castThis.foo(t);
    }
    

    和:
    template<typename T>
    void Child<T>::foo(const T& t){
        std::cout << typeid(T).name() << ": " << t << '\n';
    }
    
    
    int main(){
        Parent&& handle = Child<int>();
    
        try{
            handle.foo<int>(3);
            handle.foo<char>(0);
            handle.foo<std::string>("Hello!");
        }
        catch(std::bad_cast e){
            std::cout << "bad cast caught\n";
        }
    }
    

    我们得到以下输出on both g++ 5.2.0 and clang 3.7
    i: 3
    bad cast caught
    

    这就是我们想要的。

    一旦您在这里有了简单的多态界面,实现集合就应该很容易了。我本人将自己与std::vector<std::unique_ptr<Parent>>一起使用包装器类,但最终的决定权取决于您。

    现在,因为这还不够,所以需要注意以下几点:
  • 抛出异常不利于标准控制流。如果您实际上不通过某些外部逻辑知道参数是否与基础类型匹配,则需要其他形式的错误处理。 dynamic_cast可用于强制转换引用和指针。将引用转换为非目标类型的对象将抛出std::bad_cast。强制转换指针将返回空指针。
  • 派生类中的成员函数使用与模板成员函数相同的名称来调用基类中的该成员函数,因为name lookup在C++中的工作方式。从this answer:

    The basic algorithm is the compiler will start at the type of the current value and proceed up the hierarchy until it finds a member on the type which has the target name. It will then do overload resolution on only the members of that type with the given name. It does not consider members of the same name on parent types.


  • 因此,对foo的查找将从Child<T>开始,由于它在Child<T>内找到了具有该名称的成员函数,因此它不会检查Parent或再次调用分派(dispatch)函数。
    3.我会考虑为什么在实际使用这种解决方法之前要仔细进行此操作。

    关于c++ - c++-通过抽象模板基类接口(interface)指针访问派生类方法,而接口(interface)中没有显式类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35023784/

    有关c++ - c++-通过抽象模板基类接口(interface)指针访问派生类方法,而接口(interface)中没有显式类型的更多相关文章

    1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

      我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

    2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

      总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

    3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

      类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

    4. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

      我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

    5. Ruby 方法() 方法 - 2

      我想了解Ruby方法methods()是如何工作的。我尝试使用“ruby方法”在Google上搜索,但这不是我需要的。我也看过ruby​​-doc.org,但我没有找到这种方法。你能详细解释一下它是如何工作的或者给我一个链接吗?更新我用methods()方法做了实验,得到了这样的结果:'labrat'代码classFirstdeffirst_instance_mymethodenddefself.first_class_mymethodendendclassSecond使用类#returnsavailablemethodslistforclassandancestorsputsSeco

    6. ruby - 通过 rvm 升级 ruby​​gems 的问题 - 2

      尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub

    7. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

      我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

    8. ruby - Highline 询问方法不会使用同一行 - 2

      设置:狂欢ruby1.9.2高线(1.6.13)描述:我已经相当习惯在其他一些项目中使用highline,但已经有几个月没有使用它了。现在,在Ruby1.9.2上全新安装时,它似乎不允许在同一行回答提示。所以以前我会看到类似的东西:require"highline/import"ask"Whatisyourfavoritecolor?"并得到:Whatisyourfavoritecolor?|现在我看到类似的东西:Whatisyourfavoritecolor?|竖线(|)符号是我的终端光标。知道为什么会发生这种变化吗? 最佳答案

    9. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

      我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

    10. ruby - 通过 erb 模板输出 ruby​​ 数组 - 2

      我正在使用puppet为ruby​​程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby​​不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这

    随机推荐