上下文:
受保护和公共类成员的继承是面向对象编程的基本概念.下面的简单示例说明了一个经常遇到的情况,其中类CDerived继承了类的所有公共成员,CBase并添加了自己的1个附加函数,而不更改或显式重新定义或重新定义类的任何公共成员CBase.
#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];
};
Run Code Online (Sandbox Code Playgroud)
上面的代码在所有主要编译器上编译都没有问题.
但是,如果希望" 模板化 "此代码,例如:通过参数化Arr数组的大小,那么CBase类模板的所有公共成员对CDerived符合最新C++标准的编译器上的类模板都是不可见的.
以下是问题代码:
#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
}
Run Code Online (Sandbox Code Playgroud)
请参阅:
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>::如下所示:
int FnSum(void) {
return CBase<BYTES>::Fn1() + CBase<BYTES>::Fn2() + CBase<BYTES>::Arr[0];
}
Run Code Online (Sandbox Code Playgroud)
请参阅:
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->如下所示:
int FnSum(void) {
return this->Fn1() + this->Fn2() + this->Arr[0];
}
Run Code Online (Sandbox Code Playgroud)
请参阅:
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类模板中添加一个语句,用于CBase引用的每个成员(甚至是公共的)CDerived,如下所示:
using CBase<BYTES>::Arr;
using CBase<BYTES>::Fn1;
using CBase<BYTES>::Fn2;
Run Code Online (Sandbox Code Playgroud)
请参阅:
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:通过在编译器设置中启用"许可"模式来禁用对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/DHGBpW
MSVC注意:根据本文,默认情况下,该/permissive-选项在Visual Studio 2017 v15.5(MSVC编译器v19.11)和更高版本创建的新项目中设置.默认情况下,它未在早期版本中设置,...包括最新的Godbolt.org的Compiler Explorer MSVC版本v19.10.
GCC注意:即使使用-fpermissive编译器开关,GCC v8.1编译器仍然需要类中的using CBase<BYTES>::Arr;语句CDerived(...或其他解决方案之一),以使公共Arr数组在CDerived类模板中可见...但它不需要任何额外的东西来使Fn1()和Fn2()函数可见.
MSVC非解决方案:根据本文和本文,MSVC中的编译错误来自通过符合C++标准模式(/permissive-选项)启用的两阶段名称查找.
此外,根据前一篇文章:" 该/permissive-选项隐式设置符合的两阶段查找编译器行为,但可以通过使用/Zc:twoPhase-switch 来覆盖它 ".
但是,添加两个编译器开关/permissive- /Zc:twoPhase-不会导致"模板化"问题代码在MSVC v19.14中编译,而不会在解决方案#1或#2或#3中描述添加.
MSVC v19.14:https://godbolt.org/z/BJlyA8
有关详细信息,请参阅此条目.
上述解决
方案的问题:解决方案#4不可移植,脱离了C++标准.它也是解决本地问题的全球解决方案(全局转换) - 通常是个坏主意.仅影响部分代码(例如#pragma NOtwoPhase)的编译器开关不存在.
解决方案#1具有抑制虚拟呼叫的意外副作用,因此在一般情况下不适用.
解决方案#1和#2都需要对代码进行许多冗长的添加.这导致源代码膨胀,不添加任何新功能.例如,如果CDerived类模板仅向CBase包含5个公共函数和1个成员变量的类添加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];
}
Run Code Online (Sandbox Code Playgroud)
在现实生活中,Base类模板可能包含~50个函数和许多在Derived类模板中多次引用的变量,这需要100次这样的重复编辑!
肯定有更好的办法...
解决方案#3需要较少的工作,因为它不需要CBase在CDerived代码中查找和前缀每个参考成员.的CBase成员,由使用CDerived,需要进行"再声明"有using说法只有一次多少次,这些成员都使用/在被引用的,而不管CDerived的代码.这节省了大量无意识的搜索和打字.
不幸的是,一个一致的声明using CBase<BYTES>::*使得所有受保护的和公共成员在派生类模板中可见,并不存在.
问题:
这个问题是否有一个不太简洁的便携式解决方案?例如,解决方案#5 ......
PS这个问题与这个问题有关.
有可能被投票,我会继续下去,故意不回答你的问题.事实上,我会反过来说,整个努力都是从一开始就被误导了.
您描述的场景类型,子类调用方法或引用其父类的成员,除少数特定情况外,被视为错误代码.如果你想在这个反模式上阅读更多内容,它被称为inherit-to-extend.好的答案回答作为主题的介绍
好吧,它不是那么糟糕的代码,因为它是代码气味:模糊的迹象表明在代码的基本设计中某些东西不太正确.
代码味道还可以,你不必为了避免它们中的每一个而不得不走开,而你所描述的模式在你的情况下可能真的是正确的.然而,这将是顽皮的代码,值得一个大的评论块来解释为什么在这种情况下它是好的.
跳过篮球以便更容易编写顽皮的代码只是一个坏主意.
使用宏在某种程度上简化解决方案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);
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
795 次 |
| 最近记录: |