C++ C++;抽象函数对象与std::acculate一起使用时会导致行为混乱

C++ C++;抽象函数对象与std::acculate一起使用时会导致行为混乱,c++,inheritance,C++,Inheritance,如果我有一个纯虚函子及其实现,例如: class A { public: virtual int operator()(int& k, int& k2) = 0; public: virtual ~A() = default; void DoStuff() { std::vector<int> v = {1, 2, 3}; int k = std::acc

如果我有一个纯虚函子及其实现,例如:

class A {
    public:
        virtual int operator()(int& k, int& k2) = 0;

    public:
        virtual ~A() = default;
        void DoStuff() {
            std::vector<int> v = {1, 2, 3};
            int k = std::accumulate(v.begin(), v.end(), 0, std::ref(*this));
            // ...
        }
};

class B : public A {
    protected:
        int operator()(int &k, int &k2) override {
            return k + k2;
        }
};

void DoSomething() {
    B b;
    b.DoStuff();
}
A类{
公众:
虚拟int运算符()(int&k,int&k2)=0;
公众:
virtual~A()=默认值;
void DoStuff(){
向量v={1,2,3};
int k=std::accumulate(v.begin(),v.end(),0,std::ref(*this));
// ...
}
};
B类:公共A{
受保护的:
int运算符()(int&k,int&k2)重写{
返回k+k2;
}
};
无效剂量测定法(){
B B;
b、 DoStuff();
}
我有以下意见:

  • std::acculate
    调用的最后一个参数需要用std::ref包装,这对于它来说是一个非常奇怪的地方,因为它几乎应该只在函数绑定中使用。即使我将
    DoStuff()
    重构为一个自由函数,它仍然是需要的——这意味着我不能简单地将
    *这
    放在类方法中,或者将
    a
    的常规实例(以
    B()
    的形式)放在自由函数中。在这两种情况下,编译
    DoSomething()
    将失败,并显示函子参数的
    无效抽象参数
    ,尽管我明确定义了非抽象对象
    b
    。如果我没有声明
    A
    operator()
    为纯虚拟,而是为其提供定义,
    std::acculate
    调用将调用
    A
    中的版本。一般来说,那些需要其他函数作为参数的STL函数(
    std::acculate
    std::transform
    ,等等)在未使用
    std::ref
    包装的情况下,似乎不会利用vtable进行函子类解析
  • 如果我将
    operator()
    的作用域设置为类
    A
    (基类)
    受保护的
    ,则类方法
    DoStuff()
    的编译将失败,声称
    operator()在此上下文中受保护。这直接违背了受保护范围的定义,因为它应该允许访问同一类中的类方法和变量(以及继承的变量)。请注意,将继承类的
    运算符()
    设置为受保护的
    是完全正确的,编译器可以解析为
    运算符()
    的正确实现。一般来说,那些需要其他函数作为参数的STL函数似乎以某种方式破坏了类的范围解析。请注意,在我使用
    std::ref
    包装参数后,此观察结果仍然有效
  • 有人能从编译器的角度解释这些观察结果背后的原因吗

    观察结果中的明确问题:

    • 实现类
      B
      应该是可复制和可分配的,那么为什么需要
      reference\u wrapper
    • 无论作用域如何,使用成员变量或成员函数内部的成员函数调用外部函数都可以。为什么函数参数不受可见性范围解析的约束
    • 这看起来不像是未定义的行为。应该定义并鼓励将函数对象馈送到
      std::acculate
      。我用多个编译器(GCC、clang、MSVC)尝试了这段代码,它们都产生了相同的结果。如果这些行为是有意的,那么它们是否被记录在C++标准中的某个地方作为默认继承和可见性规则的例外?
    观察#1
    std::accumulate
    重载,它接受函数对象,并将其视为
    可复制可分配的
    可复制可构造的
    值类型。看

    旧的/不正确的:在您的情况下,我认为编译器正在尝试复制类型为
    a&
    *this
    。生成的副本将具有抽象的类型
    A

    编辑:实际上,我认为模板将
    二进制操作的类型推断为
    A
    ,并试图实例化
    std::acculate(…,A op)
    。我错误地说,
    *this
    的类型是
    A&
    ,而实际上它是
    A
    。以下是我在使用时看到的错误:

    /usr/include/c++/4.9/bits/stl\u numeric.h:替换“模板”\u Tp std::acculate(\u inputierator,\u inputierator,\u Tp,\u BinaryOperation)[带\u inputierator=\u gnu\u cxx::\u normal\u迭代器;\u Tp int;\u BinaryOperation=A]:
    

    在这种情况下,使用
    std::ref
    是正确的解决方案,因为它创建了一个引用原始(具体)对象的
    std::reference\u包装<代码>标准::累积
    使用
    二进制操作
    =
    标准::参考_包装器进行实例化

    观察#2 您正在将
    std::reference\u包装中的引用
    a&
    传递到
    std::acculate
    ,该包装试图通过调用受保护的
    operator()
    来调用它。这与以下示例的情况相同:

    声明一个正常的受保护成员函数:

        protected:
           void DoProtected() { }
    
           void DoStuff() {
                Foo(*this);
            }
    
    内部
    DoStuff()
    调用非成员函数:

        protected:
           void DoProtected() { }
    
           void DoStuff() {
                Foo(*this);
            }
    
    非成员函数尝试使用
    DoProtected

    static void Foo(A& bar) {
        bar.DoProtected();
    }
    
    导致
    错误:“void A::DoProtected()”在此上下文中受保护

    如果您想保护
    operator()
    ,可以在调用受保护运算符的
    类中编写代码:

                int k = std::accumulate(v.begin(), v.end(), 0, 
                  [this](int& k, int& k2){ return (*this)(k, k2); });
    
    关于
    B::operator()
    可以将
    B::operator()
    设为受保护的原因是std::acculate看到一个
    std::reference\u包装器,并调用
    A::operator()
    ,它是公共的。C++允许覆盖具有保护的公共虚拟方法,但我不知道背后的原理。

    观察1 <