向其他文件中的现有 C++ 类添加方法

Posted

技术标签:

【中文标题】向其他文件中的现有 C++ 类添加方法【英文标题】:Add a method to existing C++ class in other file 【发布时间】:2013-09-14 17:22:41 【问题描述】:

是否可以在 C++ 中在不同的源文件中扩展一个类(添加一个方法),而无需编辑编写该类的原始源文件?

在obj-c中可以写另一个@interface AbcClass (ExtCategory) ... @end

当我尝试这样的事情时遇到编译时错误:

//Abc.h
class Abc             //This class is from a 3rd party library....
                       //  ...I don't want to edit its source file.
    void methodOne();
    void methodTwo();




//Abc+Ext.h
class Abc        // ERROR: Redefinition of 'Abc'
    void methodAdded();

我的目标是保留“Abc”名称并向其添加方法。我使用的第 3 方库中的特定类缺少一些方法,我想添加这些方法,但我未编辑源文件。

有没有办法做到这一点?我是编写 C++ 代码的新手。我熟悉它的一些语法,但了解不多。

【问题讨论】:

要命名另一种语言,C# 可以通过称为partial class 的机制将类的定义拆分到多个文件中。 【参考方案1】:

没有。这种类扩展在C++ 中是不可能的。但是您可以从原始源文件继承一个类并在源文件中添加新功能。

//Abc.h
class Abc 
    void methodOne();
    void methodTwo();
;


//Abc+Ext.h
class AbcExt : public Abc        
    void methodAdded();
;

然后你可以调用如下方法:

std::unique_ptr<AbcExt> obj = std::make_unique<AbcExt>();
obj->methodOne(); // by the virtue of base class
obj->methodAdded(); // by the virtue of derived class

【讨论】:

AbcExt obj; obj.methodOne(); obj.methodAdded(); 在我的情况下,我需要在我的方法中添加“public :”以使其工作。类 AbcExt : public Abc public: void methodAdded(); ;【参考方案2】:

有一种方法可以真正做到这一点,但它需要编译器支持#include_next。 GCC 有这个,不知道其他编译器。它还需要至少支持 C++11。

我不会说这个技巧很漂亮,但它确实有效。

确保您的包含路径在原始代码所在目录之前具有“扩展”文件所在的目录(即,如果原始Abc.hppsrc 中,则将其移动到src/some_dir)。因此,在这种情况下,您的包含目录将是 -Isrc -Isrc/some_dir

您的“扩展”代码应位于与原始代码同名的文件中。所以对于这个例子,Abc.hpp

这是扩展文件的内容:

#ifndef ABC_EXT_HPP_
#define ABC_EXT_HPP_

#include <utility>

namespace evil 
  // Search the include path for the original file.
  #include_next "Abc.hpp"


class Abc : public evil::Abc 
  public:
    /*
    // Inherit all constructors from base class. Requires GCC >=4.8.
    using evil::Abc::Abc;
    */

    /* Use the solution below if your compiler supports C++11, but not 
     * inheriting constructors.
     */
    template <class... Args>
    Abc (Args... args) : evil::ABC(std::forward<Args...>(args...))  

    ~Abc ()  

    void methodAdded ()  /* Do some magic. */ 
;

#endif // ABC_EXT_HPP_

示例中缺少一些东西,例如赋值运算符没有“转发”到基类。您可以使用与构造函数相同的技巧来做到这一点。可能还缺少其他东西,但这应该为您提供一个足够好的“简单”类的起点。

我不喜欢的一件事是创建“邪恶”命名空间。但是,匿名命名空间在这里也帮不上忙,因为在每个包含Abc.hpp 的翻译单元中都会创建一个新的匿名命名空间。如果您的基类具有例如,这将导致问题静态成员。

编辑:没关系,赋值运算符(即Abc bla = evil::Abc(9))也有效,因为evil:Abc 可以隐式转换为Abc,因为存在构造函数。

编辑 2:一旦涉及到嵌套命名空间,您可能会遇到很多麻烦。只要在原始Abc.hpp 中有#include,就会发生这种情况,因为它现在将嵌套在evil 命名空间内。如果您知道所有包含,则可以在声明 evil 命名空间之前包含它们。事情变得非常丑陋,但很快。

【讨论】:

【参考方案3】:

在当前的 C++ 中没有直接执行此操作的特定机制,但有几种方法可以实现类似的目标,但需要花费一些样板工作:

方法一:

// foo.h
class Foo 
private:      // stuff
public:       // stuff

private:
    // All this crap is private. Pretend like I didn't expose it.
    // yeah, I know, you have to compile it, and it probably adds
    // dependencies you don't want to #include, like <string>
    // or boost, but suck it up, cupcake. Stroustrup hates life.
    void internalHelper(std::string&, std::vector&, boost::everything&);
;

方法二:

// foo.h
class Foo 
private:      // stuff
public:       // stuff
;

// fooimpl.h
// Internal file, do not export with the API.
class FooImpl : public Foo 
private:      // stuff
public:       // stuff
    // So yeah, you have to go thru a cast and an extra include
    // if you want to access this. Suck it up, cupcake.
    void internalHelper(std::string&, std::vector&, boost::everything&);
;

方法三:

// foo.h
class Foo 
private:      // stuff
public:       // stuff

    // For the private api: this is the worst approach, since it
    // exposes stuff and forces include/cruft on consumers.
    friend void foo_internalHelper(std::string&, std::vector&, boost::everything&);
;

// foo.cpp

// don't make it static or anyone can make their own as a way to
// back door into our class.
void foo_internalHelper(...);

方法四:

// foo.h
class Foo 
private:      // stuff
public:       // stuff

    // No dependencies, but nothing stops an end-user from creating
    // a FooPrivate themselves...
    friend class FooPrivate;
;

// foo1.cpp
class FooPrivate 
public:
    void fooInternalHelper(Foo* f) 
       f->m_privateInternalYouCantSeeMe = "Oh, but I can";
    
;

【讨论】:

这太糟糕了-我无法抗拒-1 @DieterLücking 哪个部分? 一般的打嗝!? (我删除了关于我的提案的内容,我认为这可能不适合 SO 答案) @kfsone:感谢您的回答。我的目标是不编辑编写类的原始源文件,并且在调用添加的方法时我仍然可以使用原始类。我想将我的代码分享给其他人,而无需更新原始源文件并创建具有不同名称的派生类的实例来调用添加的方法。在 Obj-C 中这是可能的。我也希望在 C++ 中。【参考方案4】:

你不能扩展类Abc,句号!

唯一的出路是像

这样的独立函数
Abc add(const Abc& a, int b);

【讨论】:

@xuwicha 不是真的:一个常见的是 Abc operator + (const Abc&, const T&); 在 cmets 中很难格式化多行代码,所以我只是让它更简单和内联。根据您的示例代码,我可以这样调用 add():abcOne.add(5)?或者这个:add(abcOne, 5)? @xuwicha 不幸的是后者 您可以添加运算符,例如% 给别人上课。 abcOne % 5详见“非会员运营商”。【参考方案5】:

我发现c++ 在这方面比obj-c 做得更好。

我尝试了以下方法,效果很好!

关键是,将您的所有类包含在一个命名空间中,然后使用相同的类名扩展您的目标类。

//Abc.h
namespace LibraryA 
    class Abc             //This class is from a 3rd party library....
                           //  ...I don't want to edit its source file.
        void methodOne();
        void methodTwo();

    


//Abc+Ext.hpp
namespace MyProj 
    class Abc : public LibraryA::Abc 
        using Base = LibraryA::Abc;   //desc: this is to easily access the original class...
                                      //   ...by using code: Abc::Base::someOrigMethod();
        using Base::Base;             //desc: inherit all constructors.
        
    protected:
        //---added members:
        int memberAdded;
        
    public:
        //---added methods:
        void methodAdded();
        
        //---modified virtual member funcs from original class.
        void origMethod_A() override;
        
    


//Abc+Ext.cpp
namespace MyProj 
    void Abc::origMethod_A() 
        //...some code here...
        Base::origMethod_A();    //desc: you can still call the orignal method
        //...some code here...
    


//SomeSourceFile_ThatUses_Abc.cpp
namespace MyProj       //IMPT NOTE: you really need to enclose your...
                        //   ...project specific code to a namespace so you can...
                        //   ...use the version of class Abc you extended.
                        
                        
    void SomeClass::SampleFunc()
        Abc objX;                   //create MyProj::Abc object.
        objX.methodAdded();         //calls MyProj::Abc::methodAdded();
        objX.origMethod_A();        //calls MyProj::Abc::origMethod_A();
        
        Abc::Base objY;             //create Library::Abc object.
        //objY.methodAdded();       //method not existing.
        objY.origMethod_A();        //calls Library::Abc::origMethod_A();
        
        //...some code here...
    
    


//SomeModule.cpp
namespace OtherNamespace 
    void SomeOtherClass::SampleOtherFunc()
        Abc objZ;                   //create Library::Abc object.
        //objZ.methodAdded();       //method not existing.
        objZ.origMethod_A();        //calls LibraryA::Abc::origMethod_A();
    
    

您甚至可以在其他模块命名空间中以不同方式扩展 class Abc

//MyLib_ModuleA_Classes.hpp
namespace MyLib_ModuleA 
    class Abc : public LibraryA::Abc 
        //...add extensions here...
        void addedMethod_X();
        void origMethod_A() override;    //own overriden behavior specific to this ModuleA only.
    
    


//MyLib_ModuleB_Classes.hpp
namespace MyLib_ModuleB 
    class Abc : public LibraryA::Abc 
        //...add extensions here...
        void addedMethod_Y();
        void origMethod_A() override;    //own overriden behavior specific to this ModuleB only.
    
    

如果class Abc 在全局命名空间中,虽然我还没有尝试过,但我认为你可以将LibaryA::Abc 替换为::Abc

很抱歉,我已经使用这种方法大约 4 年了,它的结构非常有用。 我在c++14 中尝试过,但我认为这在c++11 中仍然可行。现在我使用了c++17,它编译得很好。我打算转换为c++20 当我使用的编译器已经完成c++20 功能时。

【讨论】:

以上是关于向其他文件中的现有 C++ 类添加方法的主要内容,如果未能解决你的问题,请参考以下文章

使用 mongoose 通过 update 方法向 mongodb 中的现有文档添加新字段

使用 mongoose 通过 update 方法向 mongodb 中的现有文档添加新字段

我们如何向 aws cdk 中的现有表添加列?

可以使用 ASM 向现有 Java 类添加方法吗?

向现有 Python 应用程序添加 IPC 支持 [关闭]

向 Grails 域类添加字段?