Objective-C @protocol equivalent in C++

2019-04-09 19:55发布

问题:

Class A has an instance of class B as a member. Sometimes the instance of class B wants to talk to class A. In Objective-C I can do:

// A.h
@interface A : NSObject <BDelegate>
@property (nonatomic, retain) B *b;
@end 

// A.m
- (void) classBsays {

}


// B.h
@protocol BDelegate
- (void) classBsays;
@end

@interface B : NSObject
@property (nonatomic, assign) id<BDelegate> delegate;
@end

// B.m
@implementation B
- (void) f {
    [delegate classBsays];
}
@end

I've done something similar in C++ using a void pointer on class B. But this misses the part that says "class B's delegate should implement such and such methods".

How can I imitate Objective-C's protocol in C++?

回答1:

The C++ equivalent of your example looks something like this:

// A.hpp
#include "B.hpp"

class A : public BDelegate {
    public:
        void classBSays ( ) { }
        B* b;
};

// B.hpp
class BDelegate {
    public:
        virtual void classBSays( ) = 0;
};
class B {
    public:
        void f ( ) { delegate->classBSays( ); }
        BDelegate* delegate;
};

Note that I've used inline implementation of the member functions here, for brevity's sake - you could equally implement A.classBSays() and B.f() in separate A.cpp and B.cpp files if you wanted.

In this example, the class BDelegate is an abstract base class (ABC), equivalent to your BDelegate protocol. By containing only pure virtual member functions (functions preceded with the keyword virtual and with the =0 suffix), it forces its subclasses to provide implementations for those methods, much as using a @required tag (or no tag) does in an Objective-C protocol. The fact that BDelegate contains only such functions is what makes it an ABC.

You can emulate the Objective-C @optional tag by specifying an empty body for the function in your ABC, which means that subclasses are not required to implement it (since it is implemented in the ABC). For example, you could emulate an optional foo method by modifying the BDelegate as follows:

@protocol BDelegate
- (void) classBsays;
@optional
- (void) foo;
@end
// Is approximately equivalent to:
class BDelegate {
    public:
        virtual void classBSays( ) = 0;
        virtual void foo( ) { }
};

Using that definition, the class A could choose whether to provide a definition for foo or not, as is desired. Note however that this is not exactly equivalent to the Objective-C @optional notation, because A will still inherit BDelegate's foo method if it doesn't provide it's own override. With the Objective-C protocol, on the other hand, A would have no such method at all unless it explicitly implements it itself.

A more thorough introduction to the subject is available here.



回答2:

You can achieve basically the same in C++ by defining an abstract base class. That is, you only define method signatures and do not provide any implementation. This requires any subclasses to implement the declared methods.

Here is your example translated to C++:

// A.h
class A : public BDelegate {
    B *b;
};

// A.m
Result A::classBsays {

}

// B.h
class BDelegate {
    virtual Result classBsays() = 0;
};

class B {
    BDelegate* delegate;
};

// B.m
void B::f {
    delegate->classBsays();
}

Note that this will probably not compile, as it is missing a few important things. You would need a constructor, pass in the B reference, probably even use a std::shared_ptr for the delegate, and so on. But the general shape is something like this.

The important part is the method declaration for BDelegate::classBsays() has the = 0 after the method signature, and has no implementation body. This means the method is pure virtual, which means a subclass must implement the method or it cannot be instantiated. (Which makes sense, otherwise you could potentially call a method that has no implementation!) Any class that has one or more pure virtual methods is itself a pure virtual class. This is very commonly used just as you describe, to define interfaces that decouple different parts of the system.