避免公共成员隐身和使用继承的类模板避免源代码膨胀/重复的更好方法? [英] A better way to avoid public member invisibility and source code bloat/repetition with inherited class templates?

查看:65
本文介绍了避免公共成员隐身和使用继承的类模板避免源代码膨胀/重复的更好方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

上下文

继承受保护和公共类成员是面向对象编程的基本概念。下面的简单示例说明了经常遇到的情况,其中类 CDerived 继承了类 CBase 的所有公共成员,并添加自己的的另外一项功能无需更改,显式声明或重新定义 CBase 类的任何公共成员。

Context:
Inheritance of protected and public class members is a fundamental concept of Object Oriented Programming. The trivial example below illustrates an often encountered situation in which the class CDerived inherits all public members of the class CBase and adds 1 additional function of its own without changing nor explicitly redeclaring nor redefining any of the public members of the CBase class.

#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];
};

上面的代码在所有主要编译器上编译都没有问题。

The code above compiles without problems on all major compilers.

但是,如果希望 模板化此代码,例如:通过参数化 Arr 数组的大小,则 CBase 类模板的所有公共成员 CDerived 符合最新C ++标准的编译器上的c $ c>类模板。

下面是问题代码:

However, if one wishes to "templatize" this code, e.g.: by parametrizing the size of the Arr array, then all public members of CBase class template become invisible to the CDerived class template on compilers that conform to the latest C++ standard.
Below is the problem code:

#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

See:
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个解决方案:

There are 4 solutions to this problem:

解决方案1 ​​:将所有引用 CBase 类模板的成员(甚至是公共的),并带有 CBase< BYTES> ::

Solution #1: Prefix all of the references to members of the CBase class template (even the public ones), with CBase<BYTES>:: like this:

 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

See:
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-> 像这样:

Solution #2: Prefix all of the references to members of the CBase class template (even the public ones), with this-> like this:

 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

See:
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 :在 CDerived 类模板内使用语句添加一个 ,对于 CDerived 引用的 CBase 的每个成员(甚至是公共成员),如下所示:

Solution #3: Add one using statement inside the CDerived class template, for each member of the CBase (even a public one) that is referenced by the CDerived, like this:

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

See:
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 ++标准的严格一致性,如下所示:

Solution #4: Disable the strict conformance to the C++ standard by enabling the "permissive" mode in the compiler settings, like this:

对于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

For MSVC v19.10 remove the switch /permissive-, see: https://godbolt.org/g/Yxw89Y
For ICC v18.0.0 add the switch -fpermissive, see: https://godbolt.org/g/DwuTb4
For GCC v8.1 add the switch -fpermissive, see: https://godbolt.org/g/DHGBpW

MSVC注意:根据 本文 ,默认情况下,<$在由Visual Studio 2017 v15.5(MSVC编译器v19.11)和更高版本创建的新项目中设置了c $ c> / permissive-选项。在较早的版本中,默认情况下未设置它,...包括最新的Godbolt.org的Compiler Explorer MSVC版本v19.10。

MSVC NOTE: According to this article, by default the /permissive- option is set in new projects created by Visual Studio 2017 v15.5 (MSVC compiler v19.11) and later versions. It is not set by default in earlier versions, ...including the latest Godbolt.org's Compiler Explorer MSVC version v19.10.

GCC注意:即使使用 -fpermissive 编译器开关,GCC v8.1编译器仍需要使用CBase< BYTES> :: Arr; CDerived 类(...或其他解决方案之一)中的c $ c>语句,以便公开发布 Arr 数组在 CDerived 类模板中可见...但是它不需要任何额外的东西即可制作 Fn1() Fn2()函数可见。

GCC NOTE: Even with the -fpermissive compiler switch, the GCC v8.1 compiler still needs the using CBase<BYTES>::Arr; statement inside the CDerived class (...or one of the other solutions) in order to make the public Arr array visible inside the CDerived class template ...but it does not need anything extra to make the Fn1() and Fn2() functions visible.

MSVC非解决方案
根据 本文 本文 ,MSVC中的编译错误通过遵循C ++标准模式( / permissive-选项)启用了两阶段名称查找消息。

另外,根据以前的文章 < c $ c> / permissive-选项隐式设置符合条件的两阶段查找编译器行为,但可以使用 / Zc:twoPhase- switch

但是添加两个编译器开关 / permissive- / Zc:twoPhase-不会导致模板化问题代码以在MSVC v19.14中进行编译,而无需在解决方案#1或#2或#3中进行描述。

MSVC v19.14: https://godbolt.org/z/BJlyA8

MSVC Non-Solution: According to this article and this article, the compilation error in MSVC comes from the Two-Phase Name Lookup being enabled by the conformance to the C++ standard mode ( the /permissive- option).
Also, according to the former article: "The /permissive- option implicitly sets the conforming two-phase lookup compiler behavior, but it can be overridden by using /Zc:twoPhase- switch".
However adding the two compiler switches /permissive- /Zc:twoPhase- does not cause the "templated" problem code to compile in MSVC v19.14, without the additions described in Solution #1 or #2 or #3.

MSVC v19.14: https://godbolt.org/z/BJlyA8

请参见 此条目 ,以了解更多详细信息。

See this entry for more details.

上述解决方案的问题

解决方案#4不可移植,并且脱离了C ++标准。这也是针对局部问题的GLOBAL解决方案(全局切换),通常是一个坏主意。不存在只影响部分代码的编译器开关(例如 #pragma NOtwoPhase )。

解决方案#1具有意想不到的副作用

解决方案#1和#2都需要对代码进行许多冗长的添加。这会导致源代码膨胀,而不会添加任何新功能。例如,如果 CDerived 类模板仅将2个函数添加到包含5个公共函数和1个成员变量的 CBase 类中,在 CDerived 中多次引用,解决方案#1要求派生类中的 14个详细代码更改/添加,如下所示:

Problems with above Solutions:
Solution #4 is not portable and breaks away from the C++ standard. It is also a GLOBAL solution (global switch) to a local problem - usually a bad idea. A compiler switch that affects only a portion of the code (e.g. #pragma NOtwoPhase) does not exist.
Solution #1 has an unintended side-effect of suppressing virtual calls, thus it is not applicable in general case.
Both solutions #1 and #2 require many verbose additions to the code. This leads to a source code bloat that does not add any new functionality. For example if the CDerived class template adds only 2 functions to a CBase class that contains 5 public functions and 1 member variable, which are referenced multiple times in CDerived, the Solution #1 requires 14 verbose code alterations/additions in the derived class, which look like this:

    #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多次这样的重复编辑!

必须有更好的方法...

In real life the Base class template might contain ~50 functions and many variables which are referenced multiple times in the Derived class template, which necessitate 100s of such repetitive edits !
There must be a better way...

解决方案#3所需的工作较少,因为它不需要查找并在<< CDerived 的代码中的code> CBase 成员。由 CDerived 使用的 CBase 成员需要用<$ c $进行重新声明。 c>使用语句仅一次,无论在 CDerived 的代码中使用或引用了这些成员多少次。

Solution #3 requires less work because it does not require finding and prefixing EVERY REFERENCE to the CBase member in the CDerived's code. The CBase members, that are used by CDerived, need to be "re-declared" with a using statement only once, regardless how many times these members are used/referenced in the CDerived's code. This saves a lot of mindless searching and typing.

不幸的是,使用CBase< BYTES> :: * >使得派生类模板中所有受保护成员和公共成员可见。

Unfortunately a blanket statement like using CBase<BYTES>::* which makes all of the protected and public members visible in the derived class template, does not exist.

问题

是否有一个较简单的便携式解决方案来解决此问题?例如解决方案5 ...

QUESTION:
Is there a less verbose portable solution to this problem ? e.g. Solution #5...

推荐答案

使用宏在某种程度上简化解决方案3。 Boost不是严格必需的,但是可以使生活更轻松。

Use macros to simplify Solution #3 somewhat. Boost is not strictly necessary, but makes life easier.

#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);

这篇关于避免公共成员隐身和使用继承的类模板避免源代码膨胀/重复的更好方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆