我昨天遇到了以下奇怪的行为。这对我来说似乎是一个编译器错误,或者我错过了什么?我正在用 Objective-C 到 C++ 适配器类包装 iPhone 的 Objective-C 类的 Facebook Connect,以便可以更方便地从我们自己的 OpenGL/C++ 代码中使用它们。
下面的代码揭示了这个问题。在下面的第一个变体中,编译器编译但弄乱了 vtables,因此调用了错误的方法。在第二个变体中,我们得到一个编译器错误,表明 gcc 被混淆了。
评论试图更详细地解释这种情况。
#include <iostream>
#import <Foundation/Foundation.h>
// An abstract C++ interface
class Foo_cpp {
public:
virtual void foo() = 0;
};
// Another abstract C++ interface
class Bar_cpp {
public:
virtual void bar() = 0;
};
// An Objective-C to C++ adaptor.
// It takes a C++ interface Foo. When it's do_foo method is called it
// delegates call to Foo::foo.
@interface Foo_objc : NSObject {
Foo_cpp* foo_cpp_;
}
@end
@implementation Foo_objc
- (id)init:(Foo_cpp*)foo {
self = [super init];
if (self) {
foo_cpp_ = foo;
}
return self;
}
- (void) do_foo {
std::cout << "do_foo: ";
foo_cpp_->foo();
}
@end
// Another Objective-C to C++ adaptor.
@interface Bar_objc : NSObject{
Bar_cpp* bar_cpp_;
}
@end
@implementation Bar_objc
- (id)init:(Bar_cpp*)bar {
self = [super init];
if (self) {
bar_cpp_ = bar;
}
return self;
}
- (void) do_bar {
std::cout << "do_bar: ";
bar_cpp_->bar();
}
@end
// Main class implements both abstract C++ interfaces (which will
// confuse the compiler as we shall see).
// It constructs two Objective-C to C++ adaptors as a members and
// tries to pass itself as a C++ delegate for these adaptors.
class Main : public Foo_cpp, public Bar_cpp {
public:
Foo_objc* foo_;
Bar_objc* bar_;
Main() {
// We try to construct two objective-c to c++ adaptors Foo_objc and
// Bar_objc.
//
// We expect output of
// [foo_ do_foo];
// [bar_ do_bar];
// to be
// do_foo: foo
// do_bar: bar
#if 0
// This variant compiles but the compiler messes up
// the vtables. When do_bar() is called, we expect
// bar() to be called via Bar_objc, but instead
// foo() is called from both adaptors.
// Output is
// do_foo: foo
// do_bar: foo !!!! Calls wrong method !!!!
foo_ = [[Foo_objc alloc] init:this];
bar_ = [[Bar_objc alloc] init:this];
[foo_ do_foo];
[bar_ do_bar];
#else
// Now, this variant tries to help the compiler by passing
// |this| via a variable of the correct interface type.
// It actually reveals the confusion that the compiler
// is having. Seems like a bug in the compiler.
Foo_cpp* iface = this;
foo_ = [[Foo_objc alloc] init:iface];
Bar_cpp* iface2 = this;
// Error we get is on the next code line.
// $ g++ -x objective-c++ -lobjc mheritance_test.mm
// mheritance_test.mm: In constructor ‘Main::Main()’:
// mheritance_test.mm:107: error: cannot convert ‘Bar_cpp*’ to ‘Foo_cpp*’ in argument passing
bar_ = [[Bar_objc alloc] init:iface2];
[foo_ do_foo];
[bar_ do_bar];
#endif
}
~Main() {
delete foo_;
delete bar_;
}
virtual void foo() {
std::cout << "foo" << std::endl;
}
virtual void bar() {
std::cout << "bar" << std::endl;
}
};
int main() {
Main m;
}
iPhone SDK 和 Mac 自己的 g++ 以及版本 4.0.1 和 4.2 都会出现问题。有什么我理解不正确或者这是g ++中的错误吗?
更新 我的示例包含指出 Tyler 和 Martin York 的意外错误,但这不是问题所在。下面是一个更新的例子。
#include <iostream>
#import <Foundation/Foundation.h>
// An abstract C++ interface
class Foo_cpp {
public:
virtual void foo() = 0;
};
// Another abstract C++ interface
class Bar_cpp {
public:
virtual void bar() = 0;
};
// An Objective-C to C++ adaptor.
// It takes a C++ interface Foo. When it's do_foo method is called it
// delegates call to Foo::foo.
@interface Foo_objc : NSObject {
Foo_cpp* foo_cpp_;
}
@end
@implementation Foo_objc
- (id)init:(Foo_cpp*)foo {
self = [super init];
if (self) {
foo_cpp_ = foo;
}
return self;
}
- (void) do_foo {
std::cout << "do_foo: ";
foo_cpp_->foo();
}
@end
// Another Objective-C to C++ adaptor.
@interface Bar_objc : NSObject{
Bar_cpp* bar_cpp_;
}
@end
@implementation Bar_objc
- (id)init:(Bar_cpp*)bar {
self = [super init];
if (self) {
bar_cpp_ = bar;
}
return self;
}
- (void) do_bar {
std::cout << "do_bar: ";
bar_cpp_->bar();
}
@end
class Main : public Foo_cpp, public Bar_cpp {
void foo() {
std::cout << "foo" << std::endl;
}
void bar() {
std::cout << "bar" << std::endl;
}
};
int main() {
Main* m = new Main;
#if 0
// Compiles but produces
// do_foo: foo
// do_bar: foo !!! incorrect method called !!!
Foo_objc* fo = [[Foo_objc alloc] init:m];
Bar_objc* bo = [[Bar_objc alloc] init:m];
#else
// Doesn't compile
Foo_objc* fo = [[Foo_objc alloc] init:(Foo_cpp*)m];
Bar_objc* bo = [[Bar_objc alloc] init:(Bar_cpp*)m];
// A line above produces following error
// mheritance_test2.mm: In function ‘int main()’:
// mheritance_test2.mm:82: error: cannot convert ‘Bar_cpp*’ to ‘Foo_cpp*’ in argument passing
#endif
[fo do_foo];
[bo do_bar];
}
更新 2 如果 init: Fooobjc 和 Barobjc 的方法被重命名为 initfoo: 和 initbar: 那么它可以正常工作,但我仍然无法解释代码有什么问题。这可能与 Objective-C 如何创建方法签名有关吗?