带私有基的函数成员指针

Function member pointer with private base

本文关键字:函数 成员 指针      更新时间:2023-10-16

下面的代码会产生编译时错误:

' base::print ':不能访问在' base_der '类中声明的私有成员

但是,我在派生类中创建了成员public。为什么这行不通呢?

#include <iostream>
using namespace std;
class base
{
public:
    int i;
    void print(int i)
    {
        printf("base in");
    }
};
class base_der : private base
{
public:
    using base::print;
};
int main()
{
    // This works:
    base_der cls;
    cls.print(10);
    // This doesn't:    
    void (base_der::* print)(int);
    print = &base_der::print; // Compile error here
}

我认为有一些相互作用的问题导致错误:

  1. 指针到成员类型具有不直观的类型转换特征
  2. using声明不会影响进入
  3. 作用域的名称类型
  4. 虽然名称base_der::print是可访问的,但类base仍然不可访问,并且在尝试转换指针到成员时,指针到成员类型中类的实际类型是考虑的一部分。

c++ 03 7.3.3"使用声明"

using声明将一个名称引入using声明所在的声明区域。该名称是其他地方声明的某个实体名称的同义词。

请注意,当名称被带入新的"区域"时,它是同义词-名称所指的类型是相同的。所以,我认为在你的例子中,名称base_der::print的类型是void (base::*)(int),而不是void (base_der::*)(int)

c++ 03标准对于指针到成员类型之间的转换也有这样的规定(4.11 "指针到成员的转换"):

类型为"指向类型为cv T的B的成员的指针"的右值,其中B是类类型,可以转换为类型为"指向类型为cv T的D的成员的指针"的右值,其中D是B的派生类(第10条)。如果B是D的不可访问基类(第11条)、二义基类(第10.2条)或虚基类(第10.1条),则需要进行这种转换的程序是病态的。转换结果指向的成员与转换前指向成员的指针指向的成员相同,但它指向基类成员,就好像基类成员是派生类的成员一样。结果指向D的b实例中的成员,因为结果的类型是"指向类型为cv T的D成员的指针",因此可以使用D对象对其解引用。结果与B的成员指针与d的子对象B解引用相同。

也注意7.3.3/13"using声明"(强调添加):

出于重载解析的目的,由using声明引入到派生类中的函数将被视为派生类的成员。特别是,隐式this形参应被视为指向派生类而不是基类的指针。这对函数的类型没有影响,在所有其他方面,函数仍然是基类的成员。

现在,生成错误的代码示例:
// This doesn't:    
void (base_der::* print)(int);
print = &base_der::print; // Compile error here

试图将"指向D成员的指针"转换为"指向B成员的指针"——这是一个错误方向的转换。如果你想一下,你就会明白为什么这个方向的转换是不安全的。类型为"指向B成员的指针"的变量可能不会用于与class D有任何关系的对象,但是如果你调用类型为"指向D成员的指针"的函数(这就是void (base_der::* print)(int)),它将正确地期望this指针将指向D对象。

无论如何,虽然我认为问题的根源是这个转换问题,我认为你得到一个抱怨的可访问性,因为当编译器试图处理转换,它首先检查base的可访问性-即使名称base_der::print(这是base::print的别名)是可访问的,因为using声明,类base仍然不是。

免责声明:本文的分析来自于对指针到成员类型的细微差别没有多少经验的人。它们是c++中一个复杂的领域,除了在最简单的场景中使用之外很难使用,而且显然有很多可移植性问题(参见Doug Clugston的文章http://www.codeproject.com/KB/cpp/FastDelegate.aspx,这篇文章已经很老了,现在可能已经解决了很多问题,但我怀疑它们还没有解决)。

当你说c++中的某些东西是比较复杂或不太容易理解的领域之一时,那说明了很多。

我不能说我知道为什么(我也不能说规范),但是clang的错误消息可能是有指导意义的:

error: cannot cast private base class 'base' to 'base_der'

所以改变成员函数的类型是有效的,至少在clang和gcc中:

void (base::* print)(int);
print = &base_der::print; // works!

这是因为,

class base_der : private base

继承是private。所以basebase_der是不可访问的。把它改成public就可以了