上下文:
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 :使用CBaseCBase<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/DHGBpW

MSVC注意:根据 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/

10-11 18:30