仅 return 类型不同的 C++ 多个接口?
C++ multiple interfaces that only differ in return type?
做一个以人类可读的方式将 .NET IL 翻译成 C++ 的实验。
这里是问题所在: C# 允许您使用相同的方法名称解析多个接口,这些接口仅 return 类型不同。 C++ 似乎不支持这一点,但是使用 vTable 无法解析两个接口(或者我错了吗?)。
我找到了一种使用模板在 C++ 中复制 C# 方法的方法,但想知道是否有一种方法不需要模板就可以解决同样的问题?模板很冗长,如果可能的话,我不希望将它们用于每种接口类型。
这里是C++版本。
template<typename T>
class IMyInterface
{
public: short (T::*Foo_IMyInterface)() = 0;
};
template<typename T>
class IMyInterface2
{
public: int (T::*Foo_IMyInterface2)() = 0;
};
class MyClass : public IMyInterface<MyClass>, public IMyInterface2<MyClass>
{
public: MyClass()
{
Foo_IMyInterface = &MyClass::Foo;
Foo_IMyInterface2 = &MyClass::IMyInterface2_Foo;
}
public: virtual short Foo()
{
return 1;
}
private: int IMyInterface2_Foo()
{
return 1;
}
};
class MyClass2 : public MyClass
{
public: virtual short Foo() override
{
return 2;
}
};
void InvokeFoo(IMyInterface<MyClass>* k)
{
(((MyClass*)k)->*k->Foo_IMyInterface)();
}
void main()
{
auto a = new MyClass2();
InvokeFoo(a);
}
这是 C++ 所基于的 C# 参考源。
interface IMyInterface
{
short Foo();
}
interface IMyInterface2
{
int Foo();
}
class MyClass : IMyInterface, IMyInterface2
{
public virtual short Foo()
{
return 1;
}
int IMyInterface2.Foo()
{
return 1;
}
}
class MyClass2 : MyClass
{
public override short Foo()
{
return 2;
}
}
namespace CSTest
{
class Program
{
static void InvokeFoo(IMyInterface k)
{
k.Foo();
}
static void Main(string[] args)
{
var a = new MyClass2();
InvokeFoo(a);
}
}
}
此 C++ 方法在以下情况下不起作用,但希望它能起作用(它更符合我的要求)。
class IMyInterface
{
public: virtual short Foo() = 0;
};
class IMyInterface2
{
public: virtual int Foo() = 0;
};
class MyClass : public IMyInterface, public IMyInterface2
{
public: virtual short Foo()
{
return 1;
}
private: int IMyInterface2::Foo()// compiler error
{
return 1;
}
};
class MyClass2 : public MyClass
{
public: virtual short Foo() override
{
return 2;
}
};
void InvokeFoo(IMyInterface* k)
{
k->Foo();
}
void main()
{
auto a = new MyClass2();
InvokeFoo(a);
}
我有一个可行的解决方案。它并不完美,但如果您正在移植,它是一种解决问题的方法。
您可以调用 void foo(int& out)
而不是调用 int foo()
,您将 return 类型放入函数的调用部分。
问题是您不能仅基于 return 类型进行重载。
见
- Tutorialspoint
- Is it possible to have different return types for a overloaded method?
- Function overloading by return type?
最后一个 Whosebug 线程指出使用运算符可以实现重载。
struct func {
operator string() { return "1"; }
operator int() { return 2; }
};
int main() {
int x = func(); // calls int version
string y = func(); // calls string version
double d = func(); // calls int version
cout << func() << endl; // calls int version
func(); // calls neither
}
不过你不能给它们命名,这很快就会变成一团糟。
参数列表必须更改。 Victor Padureau 建议使用 void return 类型并将值的类型作为引用传递给方法中的值,这将起作用。您还可以更改不同类型的方法名称。
class my_interface
{
public:
virtual short foo_short() = 0;
};
class my_interface2
{
public:
virtual int foo_int() = 0;
};
class my_class : public my_interface, public my_interface2
{
public:
short foo_short() override
{
return 1;
}
int foo_int() override
{
return 1;
}
};
class my_class2 : public my_class
{
public:
virtual short foo_short() override
{
return 2;
}
};
void InvokeFoo(my_interface* k)
{
short result = k->foo_short();
std::cout << result << std::endl;
}
void main()
{
auto a = new my_class2();
InvokeFoo(a);
}
最好的是:, 当然是一个有趣的选择。为了完整起见:假设我们无法更改基础 类' 接口,可能是因为来自不同的库 - 我们仍然需要覆盖。
嗯,首先:完全继承这两个接口可能是一个有问题的设计,您很可能会违反 single responsibility principle.
现在假设我们无论如何都需要这样做。那么一个可能的解决方法(它不符合我自己对 'elegance' 的定义 – 但至少......)是中间 类:
class BaseA { virtual ~BaseA(); virtual void foo(); };
class BaseB { virtual ~BaseB(); virtual int foo(); };
class DerivedA : public BaseA { void foo() override; };
class DerivedB : public BaseB { int foo() override; };
class Derived : public DerivedA, public DerivedB { };
现在 Derived
可以作为 BaseA
和 BaseB
并且仍然覆盖了 foo 的两个(在本例中)变体,尽管是间接的。
如果您打算进一步继承 Derived
,仍然允许覆盖 foo
函数的任一变体,那么 DerivedA
和 DerivedB
中的每个覆盖本身都会调用一个(新)虚函数 fooA
和 fooB
分别为:
class DerivedA : public BaseA
{
public:
void foo() final // better than override:
// prevents breaking the pattern again
{ fooA() };
protected:
virtual void fooA() = 0;
};
class DerivedB : public BaseB
{
public:
int foo() final { return fooB() };
protected:
virtual int fooB() = 0;
};
这个相同的技巧允许覆盖使用两个基的成员,并且肯定是 最干净的 方法来完成这件事,但是,它作为虚函数调用有一些权衡不是免费的(vtable 查找,然后是实际的函数调用)——所以如果性能更重要(好好想想,如果它 真的 是!),你可能只是在 DerivedA
和 DerivedB
直接。
如果您想要的 C++ 代码本质上类似于您的 C# 示例,我建议您添加一个额外的中间体 class MyClass_IMyInterface2
,其唯一目的是重命名已实现的方法在 C# 代码中显式显示。
因此你会得到类似的东西:
class IMyInterface2
{
public:
virtual int Foo() = 0;
};
class MyClass_IMyInterface2 : public IMyInterface2
{
protected:
int Foo() final { return IMyInterface2_Foo(); }
virtual int IMyInterface2_Foo() = 0;
};
class MyClass : public IMyInterface, public MyClass_IMyInterface2...
顺便说一句,如果有冲突,你只需要添加额外的class,如果有冲突,只需要重命名。
假设目的是模仿 C# 代码,我可能会在每次显式实现接口方法时都这样做。
您还可以根据需要调整成员的可见性。
做一个以人类可读的方式将 .NET IL 翻译成 C++ 的实验。
这里是问题所在: C# 允许您使用相同的方法名称解析多个接口,这些接口仅 return 类型不同。 C++ 似乎不支持这一点,但是使用 vTable 无法解析两个接口(或者我错了吗?)。
我找到了一种使用模板在 C++ 中复制 C# 方法的方法,但想知道是否有一种方法不需要模板就可以解决同样的问题?模板很冗长,如果可能的话,我不希望将它们用于每种接口类型。
这里是C++版本。
template<typename T>
class IMyInterface
{
public: short (T::*Foo_IMyInterface)() = 0;
};
template<typename T>
class IMyInterface2
{
public: int (T::*Foo_IMyInterface2)() = 0;
};
class MyClass : public IMyInterface<MyClass>, public IMyInterface2<MyClass>
{
public: MyClass()
{
Foo_IMyInterface = &MyClass::Foo;
Foo_IMyInterface2 = &MyClass::IMyInterface2_Foo;
}
public: virtual short Foo()
{
return 1;
}
private: int IMyInterface2_Foo()
{
return 1;
}
};
class MyClass2 : public MyClass
{
public: virtual short Foo() override
{
return 2;
}
};
void InvokeFoo(IMyInterface<MyClass>* k)
{
(((MyClass*)k)->*k->Foo_IMyInterface)();
}
void main()
{
auto a = new MyClass2();
InvokeFoo(a);
}
这是 C++ 所基于的 C# 参考源。
interface IMyInterface
{
short Foo();
}
interface IMyInterface2
{
int Foo();
}
class MyClass : IMyInterface, IMyInterface2
{
public virtual short Foo()
{
return 1;
}
int IMyInterface2.Foo()
{
return 1;
}
}
class MyClass2 : MyClass
{
public override short Foo()
{
return 2;
}
}
namespace CSTest
{
class Program
{
static void InvokeFoo(IMyInterface k)
{
k.Foo();
}
static void Main(string[] args)
{
var a = new MyClass2();
InvokeFoo(a);
}
}
}
此 C++ 方法在以下情况下不起作用,但希望它能起作用(它更符合我的要求)。
class IMyInterface
{
public: virtual short Foo() = 0;
};
class IMyInterface2
{
public: virtual int Foo() = 0;
};
class MyClass : public IMyInterface, public IMyInterface2
{
public: virtual short Foo()
{
return 1;
}
private: int IMyInterface2::Foo()// compiler error
{
return 1;
}
};
class MyClass2 : public MyClass
{
public: virtual short Foo() override
{
return 2;
}
};
void InvokeFoo(IMyInterface* k)
{
k->Foo();
}
void main()
{
auto a = new MyClass2();
InvokeFoo(a);
}
我有一个可行的解决方案。它并不完美,但如果您正在移植,它是一种解决问题的方法。
您可以调用 void foo(int& out)
而不是调用 int foo()
,您将 return 类型放入函数的调用部分。
问题是您不能仅基于 return 类型进行重载。
见
- Tutorialspoint
- Is it possible to have different return types for a overloaded method?
- Function overloading by return type?
最后一个 Whosebug 线程指出使用运算符可以实现重载。
struct func {
operator string() { return "1"; }
operator int() { return 2; }
};
int main() {
int x = func(); // calls int version
string y = func(); // calls string version
double d = func(); // calls int version
cout << func() << endl; // calls int version
func(); // calls neither
}
不过你不能给它们命名,这很快就会变成一团糟。
参数列表必须更改。 Victor Padureau 建议使用 void return 类型并将值的类型作为引用传递给方法中的值,这将起作用。您还可以更改不同类型的方法名称。
class my_interface
{
public:
virtual short foo_short() = 0;
};
class my_interface2
{
public:
virtual int foo_int() = 0;
};
class my_class : public my_interface, public my_interface2
{
public:
short foo_short() override
{
return 1;
}
int foo_int() override
{
return 1;
}
};
class my_class2 : public my_class
{
public:
virtual short foo_short() override
{
return 2;
}
};
void InvokeFoo(my_interface* k)
{
short result = k->foo_short();
std::cout << result << std::endl;
}
void main()
{
auto a = new my_class2();
InvokeFoo(a);
}
最好的是:
嗯,首先:完全继承这两个接口可能是一个有问题的设计,您很可能会违反 single responsibility principle.
现在假设我们无论如何都需要这样做。那么一个可能的解决方法(它不符合我自己对 'elegance' 的定义 – 但至少......)是中间 类:
class BaseA { virtual ~BaseA(); virtual void foo(); };
class BaseB { virtual ~BaseB(); virtual int foo(); };
class DerivedA : public BaseA { void foo() override; };
class DerivedB : public BaseB { int foo() override; };
class Derived : public DerivedA, public DerivedB { };
现在 Derived
可以作为 BaseA
和 BaseB
并且仍然覆盖了 foo 的两个(在本例中)变体,尽管是间接的。
如果您打算进一步继承 Derived
,仍然允许覆盖 foo
函数的任一变体,那么 DerivedA
和 DerivedB
中的每个覆盖本身都会调用一个(新)虚函数 fooA
和 fooB
分别为:
class DerivedA : public BaseA
{
public:
void foo() final // better than override:
// prevents breaking the pattern again
{ fooA() };
protected:
virtual void fooA() = 0;
};
class DerivedB : public BaseB
{
public:
int foo() final { return fooB() };
protected:
virtual int fooB() = 0;
};
这个相同的技巧允许覆盖使用两个基的成员,并且肯定是 最干净的 方法来完成这件事,但是,它作为虚函数调用有一些权衡不是免费的(vtable 查找,然后是实际的函数调用)——所以如果性能更重要(好好想想,如果它 真的 是!),你可能只是在 DerivedA
和 DerivedB
直接。
如果您想要的 C++ 代码本质上类似于您的 C# 示例,我建议您添加一个额外的中间体 class MyClass_IMyInterface2
,其唯一目的是重命名已实现的方法在 C# 代码中显式显示。
因此你会得到类似的东西:
class IMyInterface2
{
public:
virtual int Foo() = 0;
};
class MyClass_IMyInterface2 : public IMyInterface2
{
protected:
int Foo() final { return IMyInterface2_Foo(); }
virtual int IMyInterface2_Foo() = 0;
};
class MyClass : public IMyInterface, public MyClass_IMyInterface2...
顺便说一句,如果有冲突,你只需要添加额外的class,如果有冲突,只需要重命名。
假设目的是模仿 C# 代码,我可能会在每次显式实现接口方法时都这样做。
您还可以根据需要调整成员的可见性。