【问题标题】:C++ compile time type determinationC++ 编译时类型确定
【发布时间】:2017-05-26 10:36:26
【问题描述】:

我有两个派生自同一个基类的类。在编译时,知道哪个是基于宏定义创建的。我有另一个类是用户并调用成员函数(每个类都有不同的)。它看起来像这样:

class User() {

    void useClass( Base* p ) {
#ifdef useA
        p->aFun();
#else 
        p->bFun()
#endif
    }

class Base() {}
class A : public Base {
    void aFun();
}
class B : public Base {
    void bFun();
}
class C {
    C() {
#ifdef useA
        p = new A();
#else 
        p = new B();
#endif
    }
    Base* p; 
    User m_user;
    void doStuffWithUser() {
        user.useClass( p );
    }
}

我想减少宏的数量,所以我想知道是否有更好的方法来做到这一点。特别是,User 类中的#ifdef 对我来说看起来不太好。有没有办法在不使用宏的情况下重现它?理想情况下不需要运行时检查来确定 p 是什么类型。

编辑: 两个派生类有不同的成员需要调用,尽管继承,但不能更改。

【问题讨论】:

  • 如果您的使用仅限于您在代码中描述的内容,那么虚函数完全可以满足您的需求。
  • 您可能可以提供一个CRTP 来摆脱#ifdef 的。
  • 看起来像虚函数。可能还有 tagdispatch 和模板的东西
  • 我编辑了问题以明确这两个类具有不同的接口。这不能改变。所以虚函数在这里不起作用
  • 目前,您的代码无法编译。

标签: c++


【解决方案1】:

解决方案是visitor pattern

这个想法是有两个类:visitorvisited

visitor 用于根据对象的真实类型调用函数。 visited 是您的类层次结构的对象。

在您的示例中,您可以这样做:

class User() {

    void useClass( Base* p ) {
         p->visit(visitor);
    }


class Base() {
    virtual void visit(Visitor) = 0;
}
class A : public Base {
    void aFun();
    virtual void visit(Visitor v) override {
        v.visit(this);
    }
}
class B : public Base {
    void bFun();
    virtual void visit(Visitor v) override {
        v.visit(this);
    }
}
class Visitor {
    void visit(B* b) {
        b->bFun();
    }
    void visit(A* a) {
        a->aFun();
    }
}

通过使用visit 函数进行这种双重调度,您可以确保根据实际类型调用该函数。

我认为您的问题没有编译时解决方案,因为在useClass(现在是这样)中,没有办法(在编译时)知道p 的真实类型。如果你想要一个编译时解决方案,你需要做更多的改变。例如制作useClass 模板或重载它,这意味着你不能再用Base* 调用useClass ...

【讨论】:

    【解决方案2】:

    A 和 B 共享一个公共基类这一事实无关紧要,因为它们有不同的接口,您正在使用。

    我会将 C 设为模板并存储指向派生类而不是基类的指针:

    template<typename T>
    class CT {
    public:
        CT() {
            p = std::make_unique<T>();
        }
        std::unique_ptr<T> p;
        User m_user;
        void doStuffWithUser() {
            user.useClass(p);
        }
    };
    

    然后你可以简单地重载 useClass() 来接受 A 或 B:

    class User {
    public:
        void useClass(A* p) {
            p->aFun();
        }
        void useClass(B* p) {
            p->bFun();
        }
    };
    

    现在您只有一个编译时开关:

    #ifdef useA
    using C = CT<A>;
    #else
    using C = CT<B>;
    #endif
    

    【讨论】:

    • 这看起来是一个工作正常的解决方案,但现在我有一个问题,我有一个别名使用 sptrC = std::shared_ptr;现在抱怨它需要一个类型,但得到了'C'。看起来我必须将别名设为 n 别名模板,然后使用 sptrC 或 sptrC 声明它,这意味着我将在每个声明中都有一个 #ifdef。有办法解决吗?
    • 我没有关注。你能包括你得到的确切代码和错误吗?
    • 我的意思是,如果我有类似 """template; class Test {}; using pTest = Test*;""" 之类的东西,我需要指定为例如 using pTest = 测试*;
    【解决方案3】:

    您可以将 aFun 和 bFun 重命名为 Fun 并使其成为虚拟(也将其添加到基类中)并在 useClass 中,使用 Fun 方法,编译器将确定使用哪种方法。 这将消除第一个宏。

    第二个也许你应该用其他方式重写它,所以你根本不会使用宏。我不认为你可以在没有宏的情况下重现这种行为。 也许你应该有一些你给构造函数的标志,1 来创建对象 A 或 0 来创建对象 B 并在运行时从用户那里获取这个标志。

    编辑 因此,也许您可​​以创建函数 Fun,在 A 类中调用 aFun,在 B 类中调用 bFun。

    【讨论】:

    • 无法更改派生类的接口
    【解决方案4】:

    您可以为User 类创建模板,并将其专门用于 A 类和 B 类:

    template<typename T>
    class User
    {
        void useClass( Base* p );
    }
    
    template<>
    class User<A>
    {
        void useClass( Base* p ) {p->aFun();}
    };
    
    template<>
    class User<B>
    {
        void useClass( Base* p ) {p->bFun();}
    };
    

    现在在 C 班:

    template<typename T>
    class C {
        C() {
           p = new T();
        }
        Base* p; 
        User<T> m_user;
        void doStuffWithUser() {
            m_user.useClass( p );
        }
    }
    

    最后一点,请避免使用 new 运算符。试试 std::unique_ptr 或 std::shared_prt

    附言。我没有测试过这段代码

    【讨论】:

      【解决方案5】:

      如果您不想更改任何界面,您可以使用单个#ifdef

      class Base {};
      
      class A : public Base {
      public:
         void aFun(){}
      };
      
      class B : public Base {
      public:
        void bFun(){}
      };
      
      #ifdef useA
        typedef A impl_type;
        auto correct_func = &impl_type::aFun;
      #else
        typedef B impl_type;
        auto correct_func = &impl_type::bFun;
      #endif
      
      class User {
      public:
        void useClass( Base* p ) {
          auto pointer = (static_cast<impl_type*>(p));
          (pointer->*correct_func)();
         }
      };
      
      class C {
        C() {
          p = new impl_type();
        }
        Base* p;
        User m_user;
        void doStuffWithUser() {
          m_user.useClass( p );
        }
      };
      

      【讨论】:

        【解决方案6】:

        也许您可以在 A 和 B 中将两个函数命名为相同的名称并将其设为虚拟,因此 useClass 将只调用需要的函数。

        喜欢

        class User() {
            void useClass( Base* p ) {
                p->fun();
            }
        };    
        class Base() {
            virtual void fun() = 0;
        };
        class A : public Base {
            void fun();
        };
        class B : public Base {
            void fun();
        };
        

        您还可以使用某种 constexpr 函数(如果您使用的是 c++11 标准或更高版本)来确定 p 是什么类型。

        编辑: 看到评论后,我认为您可能可以留下您的 aFun()、bFun(),只需添加一些将派生并调用特定于类型的函数的 fun() 函数。 此外,尝试创建一些具有相同接口的适配器类(如在 gof 模式中)可能会有所帮助。

        Edit2:我的意思是可能有一些类似的功能

        constexpr Base* chooseType(int a){
            if(a == 0){
                return new A();
            } else {
                return new B();
            }
        }
        /////
        C() {
            int case = 0;
            p = chooseType(case);
        }
        

        它会在编译时被调用,作为类的选择。

        【讨论】:

        • 我无法更改接口。你能用某种 constexpr 详细说明你的意思吗......
        • constexpr 在调用 new 的函数上毫无用处。
        【解决方案7】:

        如果你不能改变接口,想要摆脱#ifdefs,并且有正在使用的类型的编译时保证,没有运行时检查 - 我建议使用templates的组合,和重载的函数。

        首先,我会将C 类更改为template

        template<typename Type>
        class C
            {
            static_assert(std::is_base_of<Base, Type>::value, "Template argument of C is not base of Base!");
            public:
                C () {p = new Type;}
                ~C() {delete p;}
        
                void fun () {u.useClass (p);}
        
            private:
                Type* p;
                User u;
            };
        

        然后,将更改 User 类以在具有重载函数的 Base 的不同可能实现之间切换:

        class User
            {
            public:
                void useClass (A* p) {p->aFun();}
                void useClass (B* p) {p->bFun();}
            };
        

        然后,您将创建 C 的对象,如下所示:

        C<A> ca;
        

        如果您忘记实现特定类型的useClass,或尝试在C 中使用错误的类型(即不是从Base 继承),您将收到编译时错误。

        另外,如果你想在Base的一些子类之间切换,有非默认构造函数,你可以传递一个函子(例如std::function&lt;Type*()&gt;)给C构造函数,然后使用创建一个对象。

        这样的构造函数可能看起来像:

        C (std::function<Type* ()> function) {p = function();}
        

        它的用法如下:

        C<Z> cz ([&]{return new Z(someInt);});
        

        【讨论】:

          猜你喜欢
          • 2011-02-07
          • 1970-01-01
          • 1970-01-01
          • 2015-11-21
          • 1970-01-01
          • 2011-11-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多