42

在 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++ 代码的新手。我熟悉它的一些语法,但不太了解。

4

5 回答 5

37

不,这种类扩展在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
于 2013-09-14T17:26:37.317 回答
12

有一种方法可以实际做到这一点,但它需要编译器支持#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:一旦涉及嵌套命名空间,您可能会遇到很多麻烦。只要#include在 original中有一个,就会发生这种情况Abc.hpp,因为它现在将嵌套在evil命名空间内。如果您知道所有包含,则可以在声明evil命名空间之前包含它们。事情变得非常丑陋,但很快。

于 2017-02-16T18:21:41.093 回答
2

在当前的 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";
    }
};
于 2013-09-14T18:09:31.640 回答
2

你不能延长课程Abc,期间!

唯一的出路是独立的功能,如

Abc add(const Abc& a, int b);
于 2013-09-14T18:29:49.340 回答
1

我发现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的功能。

于 2021-05-14T03:28:39.197 回答