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
- 无论作用域如何,使用成员变量或成员函数内部的成员函数调用外部函数都可以。为什么函数参数不受可见性范围解析的约束
- 这看起来不像是未定义的行为。应该定义并鼓励将函数对象馈送到
。我用多个编译器(GCC、clang、MSVC)尝试了这段代码,它们都产生了相同的结果。如果这些行为是有意的,那么它们是否被记录在C++标准中的某个地方作为默认继承和可见性规则的例外?std::acculate
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
<