问题 Objective-C在C ++中的类别构造或技术?


Objective-C类别功能允许程序员添加未在原始类定义中定义的新方法。

我可以在C ++上存档类似的功能(语言构造或某些技术)吗?

主要关注的是一致的方法调用语法(. 要么 -> 运营商)。


12059
2018-02-19 07:35


起源

是的,如果您有权访问类定义。不,否则。 - Alok Save
在运行时,您无法获得任何语言支持。但是,您可以使用例如包含函数指针的映射。但那是相当麻烦且容易出错的。 - Michael Wild
@AlokSave你的意思是直接修改源代码吗?这可能是一种选择。 - Eonil
@MichaelWild它不需要在运行时完成。我在用着 波科,我根本不想修改它的源代码,因为我不希望外部依赖项维护的额外复杂性。如果有一种方法可以在不修改现有类代码的情况下添加新方法,那么它也会很好。 - Eonil
那是不可能的。您可以做的最好的事情是编写非成员函数,这些函数将指向实例的指针作为其第一个参数。 - leemes


答案:


让我们考虑扩展以下类:

struct A {
    int x, y;
    A(int x, int y) : x(x), y(y) {}
};

您可以从此类继承或编写包含此类实例的包装类。在大多数情况下,继承是一种可行的方式,作为包装类  一个但是它 包装 (包含)A

用C ++ 11移动语义,提升实例 A 到一个子类 B (继承 A)将是高效的,不需要复制实例 A

class B : public A {
public:
    B (A &&a) : A(a), someOtherMember(a.x + a.y) {}

    // added public stuff:
    int someOtherFunction() const { return someOtherMember; }

private:
    // added private stuff:
    int someOtherMember;
};

完整代码示例: http://ideone.com/mZLLEu

当然我添加的功能有点傻(而且会员更多,因为它不尊重原始成员的进一步变化 x 和 y),但你应该知道我想要展示什么。

注意构造函数 B (A &&a) 我称之为“促进构造函数”(这不是标准术语)。一般, B (B &&b) 是一个 移动构造函数, 哪一个 移动 提供的内容 B 实例成新的 B 即将建成。我使用移动语义来 移动 一个例子 A (已被其他功能退回)  超级班 A 的 B

实际上,你可以推广 A 至 B 同时可以使用 B 作为一个 A

与Soonts的回答相反,我的解决方案也适用于添加的虚拟表,因为它不依赖于不安全的指针转换。


7
2018-02-19 08:03



我明白 移动ctor 在语义上创建一个新对象,如果可能,编译器可以重用现有对象。和你的 促进ctor 也有相同的编译器优化机会。我的理解是否正确? - Eonil
A(A &&a) 是移动ctor A。这将是一个旧的 A 变成新的 A。从语义上讲,它是一个新对象,但由于移动语义,它可以“窃取”旧内容 A。我的方法的想法是移动旧的 A 变成一个 B。以来 B 继承自 A,它必须初始化一个 A 无论如何。我使用移动ctor A 在某些构造函数中 B 我想打电话 促进ctor (我提出这个词,因为它有效地促进了现有的 A 到了 B 没有触及的内容 A 只要它的移动ctor写得很好)。 - leemes
这里有一个微妙的地方 - 考虑你也有例如 class C : public A,使用移动构造函数 A。在这种情况下,你拥有的对象是一个 A, 一个 B或者a C。它不可能两者兼而有之 B 和 C 与此同时。 - alastair


另一个选项,可能不被某些人视为“干净”(尽管在我看来)但仍然完成相同的事情,就是使用静态类。重要的是要记住,当我们创建一个成员函数时,幕后真正发生的是编译生成一个函数,其中对象(又名“this”)是第一个参数。因此,我们可以做同样的事情来扩展我们的类的功能而不从它衍生出来。

class Something
{
public:
   Something()
   ~Something()
}

// In objective-c you may call this category Something+Utils
class SomethingUtils
{
   // You can use a pointer, or a reference here, your call.
   static int GetSomethingElse(Something *something, int parameter);
}

这将实现与类别相同的意图:扩展类对象的功能,而不必创建新的派生类。您将无法访问私有或受保护的成员函数和变量,但无论如何您无法在objective-c中执行此操作,因此在该方面没有任何丢失(如果您尝试使用私有或受保护的成员状态,则完全错过了类别的重点。你将无法使用。和 - >运算符,但在我看来,这比为了添加一些实用方法而导出一个新类型更好。


5
2018-03-22 02:08



这在语法上很难看,但我不得不同意这是最实用的选择。即使我适应@leemes的解决方案,但我会编写一些像这样的实用程序类,并将它包装起来。 - Eonil
在某些方面更具风格吸引力的轻微变化是使用命名空间。 - Nate Chandler
我也会添加Eonil,虽然你可能认为它在语法上很难看(我个人没有),但它在语法上是清晰的。很明显,这是一个类扩展而不是核心方法,一个问题obj-c类别受到影响(你必须按照方法到头文件来实现它是一个类别)。至于丑陋,我不会辩论,因为我们可能永远不会看到那一个:) - Adam Eskreis


C ++具有继承性。另外,我曾多次使用以下技巧来扩展#import“progid:...”指令生成的类:

// This one is part of external framework, or auto-generated, or dllimport, or #import, etc..
class A
{
    protected double m_x;
};

// This one is the extension class. Make sure you only add non-virtual methods here.
// Static methods and static data members are OK as well.
class __declspec( novtable ) B: public A
{
public:
    double getSquare(){ return m_x * m_x; }
    __declspec( property( get = getSquare ) ) double square;
};

// Usage example
double someFunc( A& arg )
{
    B& b = static_cast<B&>( arg ); // Note we've not constructed any instance of B, just casted.
    return b.square;
}

1
2018-02-19 07:54



哦,实际上我只想出类似的(也许是相同的?)技术,并发布了一个新的 题。我可以通过标准对待这种技术是否安全? - Eonil
__declspec 和 property 不属于ISO标准C ++。 - Bo Persson
我绝对相信它在Visual Studio 2005和2008中有效。我不知道它是标准的,说实话我不在乎。 “标准C ++”只是另一个漏洞抽象。要确保您的C ++代码使用编译器A,B和C构建,您必须以这种方式开发和测试它。 - Soonts
@Soonts我同意你的看法,如果编译器A,B,C支持某些功能X但标准中没有,那么使用比标准中的功能Y更好,而不是在你想要支持的编译器中实现。但是,代码中的功能仅限于MSVC,这是一个巨大的限制。 - leemes
好吧,这个 property 似乎只是吸气剂和制定者的一个方便的包装。有效, b.square 只是语法上的含糖 b.getSquare() 什么时候阅读另一件事(novtable)似乎是一个断言,不应该使用vtable;如果你编写虚函数,我想你会得到一个编译错误,因为它们需要一个vtable。这种方法的问题在于内存布局 B 必须从头开始 A 如果要添加vtable或成员(技术上,vtable指针类似于成员),则不是这种情况。 - leemes


我有一个 几乎 一致的呼叫约定 我在闪电话题中谈到的一个想法 大约一年前:

(如果没有他的评论,这个介绍就没有意义了 - 等到它到达C ++位)。

请注意,这些材料并不打算被认真对待 - 尽管有些材料不可避免地存在;-)


0
2018-05-26 13:55