上下文:
protected 和公共(public)类成员的继承是面向对象编程的基本概念。下面的简单示例说明了一种经常遇到的情况,其中CDerived
类继承了CBase
类的所有公共(public)成员,并为其自身的添加了1个附加功能,而却没有更改,未显式声明或重新定义CBase
类的任何公共(public)成员。
#include <stdio.h>
class CBase
{
public:
char Arr[32];
int Fn1(void) {
return Arr[1] ^ Arr[sizeof(Arr)-1];
}
int Fn2(void) {
return Arr[2] ^ Arr[sizeof(Arr)-2];
}
};
class CDerived : public CBase
{
public:
int FnSum(void) {
return Fn1() + Fn2();
}
};
int main(void)
{
CDerived ddd;
printf("%d\n", ddd.Fn1());
printf("%d\n", ddd.Fn2());
printf("%d\n", ddd.FnSum());
return (int)ddd.Arr[0];
};
上面的代码可以在所有主要编译器上毫无问题地进行编译。
但是,如果希望“模板化”此代码,例如:通过参数化
Arr
数组的大小,则在符合最新C++的编译器上,CBase
类模板的所有公共(public)成员对CDerived
类模板都是不可见的。标准。下面是问题代码:
#include <stdio.h>
template <unsigned int BYTES>
class CBase
{
public:
char Arr[BYTES];
int Fn1(void) {
return Arr[1] ^ Arr[sizeof(Arr)-1];
}
int Fn2(void) {
return Arr[2] ^ Arr[sizeof(Arr)-2];
}
};
template <unsigned int BYTES>
class CDerived : public CBase<BYTES>
{
public:
int FnSum(void) {
return Fn1() + Fn2() + Arr[0]; // ERRORs: identifiers "Fn1" and "Fn2" and "Arr" are NOT found !
}
};
int main(void)
{
CDerived<32> ddd;
printf("%d\n", ddd.Fn1()); //No error here
printf("%d\n", ddd.Fn2()); //No error here
printf("%d\n", ddd.FnSum());
return (int)ddd.Arr[0]; //No error here
}
看到:
MSVC v19.10:https://godbolt.org/g/eQKDhb
ICC v18.0.0:https://godbolt.org/g/vBBEQC
GCC v8.1:https://godbolt.org/g/GVkeDh
有4个解决此问题的方法:
解决方案#1 :使用
CBase
对CBase<BYTES>::
类模板的成员(甚至是公共(public)的)的所有引用添加前缀: int FnSum(void) {
return CBase<BYTES>::Fn1() + CBase<BYTES>::Fn2() + CBase<BYTES>::Arr[0];
}
看到:
MSVC v19.10:https://godbolt.org/g/48ZJrj
ICC v18.0.0:https://godbolt.org/g/BSPcSQ
GCC v8.1:https://godbolt.org/g/Vg4SZM
解决方案#2 :使用
CBase
将对this->
类模板的成员(甚至是公共(public)的)的所有引用添加前缀: int FnSum(void) {
return this->Fn1() + this->Fn2() + this->Arr[0];
}
看到:
MSVC v19.10:https://godbolt.org/g/oBs6ud
ICC v18.0.0:https://godbolt.org/g/CWgJWu
GCC v8.1:https://godbolt.org/g/Gwn2ch
解决方案#3 :对于
using
引用的CDerived
的每个成员(甚至是公共(public)的),在CBase
类模板内添加一个CDerived
语句,如下所示:using CBase<BYTES>::Arr;
using CBase<BYTES>::Fn1;
using CBase<BYTES>::Fn2;
看到:
MSVC v19.10:https://godbolt.org/g/gJT8cX
ICC v18.0.0:https://godbolt.org/g/1RK84A
GCC v8.1:https://godbolt.org/g/d8kjFh
解决方案#4 :通过在编译器设置中启用“permissive”模式来禁用对C++标准的严格遵循,如下所示:
对于MSVC v19.10,删除开关
/permissive-
,请参阅:https://godbolt.org/g/Yxw89Y对于ICC v18.0.0,添加开关
-fpermissive
,请参阅:https://godbolt.org/g/DwuTb4对于GCC v8.1,添加开关
-fpermissive
,请参阅:https://godbolt.org/g/DHGBpWMSVC注意:根据 this article ,默认情况下,在Visual Studio 2017 v15.5(MSVC编译器v19.11)和更高版本创建的新项目中设置了
/permissive-
选项。较早的版本(包括最新的Godbolt.org的Compiler Explorer MSVC版本v19.10)中没有默认设置。GCC注意:即使使用
-fpermissive
编译器开关,GCC v8.1编译器仍需要using CBase<BYTES>::Arr;
类(...或其他解决方案之一)中的CDerived
语句,以使公共(public)Arr
数组在CDerived
类模板中可见...但是不需要任何额外的操作即可显示Fn1()
和Fn2()
函数。MSVC非解决方案:
根据 this article 和 this article ,MSVC中的编译错误来自通过遵循C++标准模式(
/permissive-
选项)启用了两阶段名称查找。另外,根据former article:“
/permissive-
选项隐式设置符合条件的两阶段查找编译器行为,但是可以使用/Zc:twoPhase-
开关覆盖它”。但是,添加两个编译器开关
/permissive- /Zc:twoPhase-
不会导致“模板化”的问题代码在MSVC v19.14中进行编译,而无需解决方案#1或#2或#3中描述的增加。MSVC v19.14:https://godbolt.org/z/BJlyA8
有关更多详细信息,请参见 this entry 。
以上解决方案的问题:
解决方案#4不可移植,并且脱离了C++标准。这也是针对局部问题的GLOBAL解决方案(全局切换),通常是一个坏主意。仅影响部分代码(例如
#pragma NOtwoPhase
)的编译器开关不存在。解决方案#1具有抑制虚拟 call 的意外副作用,因此不适用于一般情况。
解决方案#1和#2都需要对代码进行许多冗长的添加。这导致源代码膨胀,没有添加任何新功能。例如,如果
CDerived
类模板仅向包含5个公共(public)函数和1个成员变量的CBase
类中添加2个函数,这些函数在CDerived
中多次引用,则解决方案#1要求在派生类中进行14个冗长的代码更改/添加 ,如下所示: #include <stdio.h>
template <unsigned int BYTES>
class CBase
{
public:
char Arr[BYTES];
CBase() {
for (size_t i=1; i<sizeof(Arr); i++)
Arr[i] = Arr[i-1]+(char)i;
}
int Fn1(void) {
return Arr[1] ^ Arr[sizeof(Arr)-1];
}
int Fn2(void) {
return Arr[2] ^ Arr[sizeof(Arr) - 2];
}
int Fn3(void) {
return Arr[3] ^ Arr[sizeof(Arr) - 3];
}
int Fn4(void) {
return Arr[4] ^ Arr[sizeof(Arr) - 4];
}
int Fn5(void) {
return Arr[5] ^ Arr[sizeof(Arr) - 5];
}
};
template <unsigned int BYTES>
class CDerived : public CBase<BYTES>
{
public:
int FnSum(void) {
return CBase<BYTES>::Fn1() +
CBase<BYTES>::Fn2() +
CBase<BYTES>::Fn3() +
CBase<BYTES>::Fn4() +
CBase<BYTES>::Fn5() +
CBase<BYTES>::Arr[0] +
CBase<BYTES>::Arr[1] +
CBase<BYTES>::Arr[2];
}
int FnProduct(void) {
return CBase<BYTES>::Fn1() *
CBase<BYTES>::Fn2() *
CBase<BYTES>::Fn3() *
CBase<BYTES>::Fn4() *
CBase<BYTES>::Fn5() *
CBase<BYTES>::Arr[0] *
CBase<BYTES>::Arr[1] *
CBase<BYTES>::Arr[2];
}
};
int main(void)
{
CDerived<32> ddd;
printf("%d\n", ddd.FnSum());
printf("%d\n", ddd.FnProduct());
return (int)ddd.Arr[0];
}
在现实生活中,基类模板可能包含〜50个函数和许多变量,在派生类模板中多次引用了这些变量,因此需要进行100多次此类重复编辑!
肯定有更好的办法...
解决方案#3所需的工作较少,因为它不需要在
CBase
的代码中查找和向CDerived
成员添加任何引用。 CBase
所使用的CDerived
成员只需使用using
语句“重新声明”一次即可,而不管CDerived
的代码中使用或引用了这些成员多少次。这样可以节省大量的盲目搜索和键入操作。不幸的是,不存在像
using CBase<BYTES>::*
这样的笼统声明,该声明使所有 protected 成员和公共(public)成员在派生类模板中可见。问题:
有没有较冗长的便携式解决方案来解决此问题?例如解决方案#5 ...
最佳答案
使用宏在某种程度上简化解决方案3。提升并非严格必要,但会使生活更轻松。
#include <boost/preprocessor.hpp>
#define USING_ONE(r, base, member) \
using base::member;
#define USING_ALL(base, ...) \
BOOST_PP_SEQ_FOR_EACH( \
USING_ONE, base, \
BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__) \
)
// Near CBase<BYTES>
#define USING_CBASE(param) USING_ALL(CBase<param>, Arr, Fn1, Fn2, Fn3, Fn4, Fn5)
// In CDerived<BYTES>, in a `public:` section
USING_CBASE(BYTES);
关于c++ - 避免公共(public)成员隐身和继承类模板的源代码膨胀/重复的更好方法?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/50321788/