防止在存在菱形继承的情况下进行冗余函数调用
Preventing redundant function calls in the presence of diamond inheritance
存在菱形继承的情况下防止冗余函数调用的好策略是什么? 具体来说,假设我们有一个程序:
#include <iostream>
struct A {
int a;
A(int a_) : a(a_) {}
virtual void print() {
std::cout << "a: " << a << std::endl;
}
};
struct B : virtual public A {
int b;
B(int a_,int b_) : A(a_), b(b_) {}
virtual void print() {
A::print();
std::cout << "b: " << b << std::endl;
}
};
struct C : virtual public A {
int c;
C(int a_,int c_) : A(a_), c(c_) {}
virtual void print() {
A::print();
std::cout << "c: " << c << std::endl;
}
};
struct D : public B,public C {
int d;
D(int a_,int b_,int c_,int d_) : A(a_), B(a_,b_), C(a_,c_), d(d_) {}
void print() {
B::print();
C::print();
std::cout << "d: " << d << std::endl;
}
};
int main() {
D d(1,2,3,4);
d.print();
}
当我们调用 d.print() 时,我们得到:
a: 1
b: 2
a: 1
c: 3
d: 4
其中 a 已打印两次。 有没有防止这种情况的好方法? 当然,我们可以使用如下代码手动连接连接:
#include <iostream>
struct A {
int a;
A(int a_) : a(a_) {}
virtual void print_() {
std::cout << "a: " << a << std::endl;
}
virtual void print() {
A::print_();
}
};
struct B : virtual public A {
int b;
B(int a_,int b_) : A(a_), b(b_) {}
virtual void print_() {
std::cout << "b: " << b << std::endl;
}
virtual void print() {
A::print_();
B::print_();
}
};
struct C : virtual public A {
int c;
C(int a_,int c_) : A(a_), c(c_) {}
virtual void print_() {
std::cout << "c: " << c << std::endl;
}
virtual void print() {
A::print_();
C::print_();
}
};
struct D : public B,public C {
int d;
D(int a_,int b_,int c_,int d_) : A(a_), B(a_,b_), C(a_,c_), d(d_) {}
virtual void print_() {
std::cout << "d: " << d << std::endl;
}
virtual void print() {
A::print_();
B::print_();
C::print_();
D::print_();
}
};
int main() {
D d(1,2,3,4);
d.print();
}
正确输出
a: 1
b: 2
c: 3
d: 4
但我想知道是否有更好的方法。 就出现这种情况而言,想象一下对象 A、B、C 和 D 很复杂,需要能够将自身写入磁盘的情况。 我们只想为每个 A、B、C 和 D 编写一次输出代码,重要的是 D 不要两次写入有关 A 的信息。
<---编辑>
--->这是解决问题的另外两种方法,但它们仍然有点迟钝。 第一个来自克里斯蒂安,涉及设置一个标志,以确定A是否已被打印
#include <iostream>
struct A {
int a;
bool have_printed;
A(int a_) : have_printed(false), a(a_) {}
virtual void print() {
if(have_printed) return;
std::cout << "a: " << a << std::endl;
have_printed=true;
}
void clear() {
have_printed=false;
}
};
struct B : virtual public A {
int b;
B(int a_,int b_) : A(a_), b(b_) {}
virtual void print() {
A::print();
std::cout << "b: " << b << std::endl;
}
};
struct C : virtual public A {
int c;
C(int a_,int c_) : A(a_), c(c_) {}
virtual void print() {
A::print();
std::cout << "c: " << c << std::endl;
}
};
struct D : public B,public C {
int d;
D(int a_,int b_,int c_,int d_) : A(a_), B(a_,b_), C(a_,c_), d(d_) {}
void print() {
B::print();
C::print();
std::cout << "d: " << d << std::endl;
}
};
int main() {
D d(1,2,3,4);
d.clear();
d.print();
}
这将正确输出。 第二种方法更复杂,但可能允许结构增长。 基本上,我们将打印机与类分离出来,然后在每个对象中注册一个打印机列表。 当我们想要打印时,我们迭代打印机列表,然后为我们提供正确的输出。 我觉得这使用了太多的机器,但我会包括以防其他人有更好的想法:
// A simple unary function. Technically, the stl version doesn't require
// the operator
template <typename A,typename B>
struct unary {
virtual B operator () (A a) {};
};
struct A {
// Data
int a;
// A list of pointers to unary functions. We need pointers to unary
// functions rather than unary functions since we need the printer
// to be polymorphic.
std::list < unary<A*,void>* > printers;
A(int a_);
// We actually end up allocating memory for the printers, which is held
// internally. Here, we free that memory.
~A() {
for(std::list < unary<A*,void>* >::iterator printer
=printers.begin();
printer != printers.end();
printer++
)
delete (*printer);
}
private:
// Require for the dynamic cast used later
virtual void ___dummy() {};
};
// Prints out the data for A
struct A_Printer : public unary<A*,void>{
void operator () (A* a) {
std::cout << "a: " << a->a << std::endl;
}
};
// Adds the printer for a to the list of printers
A::A(int a_) : a(a_) {
printers.push_back(new A_Printer());
}
// Now that the structure is setup, we just need to define the data for b,
// it's printer, and then register the printer with the rest
struct B : virtual public A {
int b;
B(int a_,int b_);
};
struct B_Printer : public unary<A*,void>{
void operator () (A* b) {
std::cout << "b: " << dynamic_cast <B*>(b)->b << std::endl;
}
};
B::B(int a_,int b_) : A(a_), b(b_) {
printers.push_back(new B_Printer());
}
// See the discussion for B
struct C : virtual public A {
int c;
C(int a_,int c_);
};
struct C_Printer : public unary<A*,void>{
void operator () (A* c) {
std::cout << "c: " << dynamic_cast <C*>(c)->c << std::endl;
}
};
C::C(int a_,int c_) : A(a_), c(c_) {
printers.push_back(new C_Printer());
}
// See the discussion for B
struct D : public B,public C {
int d;
D(int a_,int b_,int c_,int d_);
};
struct D_Printer : public unary<A*,void>{
void operator () (A* d) {
std::cout << "d: " << dynamic_cast <D*>(d)->d << std::endl;
}
};
D::D(int a_,int b_,int c_,int d_) : A(a_), B(a_,b_), C(a_,c_), d(d_) {
printers.push_back(new D_Printer());
}
// This actually prints everything. Basically, we iterate over the printers
// and call each one in term on the input.
void print(A* a) {
for(std::list < unary<A*,void>* >::iterator printer
=a->printers.begin();
printer != a->printers.end();
printer++
)
(*(*printer))(a);
}
int main() {
D d(1,2,3,4);
// This should print 1,2,3,4
print(&d);
}
<---编辑2--->
TMPEARCE有一个好主意,在组装之前将所有信息累积到哈希表中。 通过这种方式,可以检查个人信息是否已创建并防止冗余。 我认为当信息可以轻松组装时,这是一个好主意。 如果不是这种情况,可能会有轻微的变化,它结合了tmpearce和Cristian的想法。 在这里,我们传递一个集合(或哈希表,或其他什么),用于跟踪函数是否已被调用。 通过这种方式,我们可以检查是否已计算出某些函数。 它不需要永久状态,因此多次调用应该是安全的:
#include <iostream>
#include <set>
struct A {
int a;
A(int a_) : a(a_) {}
virtual void print_(std::set <std::string>& computed) {
if(computed.count("A") > 0) return;
computed.insert("A");
std::cout << "a: " << a << std::endl;
}
void print() {
std::set <std::string> computed;
print_(computed);
}
};
struct B : virtual public A {
int b;
B(int a_,int b_) : A(a_), b(b_) {}
virtual void print_(std::set <std::string>& computed) {
A::print_(computed);
if(computed.count("B") > 0) return;
computed.insert("B");
std::cout << "b: " << b << std::endl;
}
};
struct C : virtual public A {
int c;
C(int a_,int c_) : A(a_), c(c_) {}
virtual void print_(std::set <std::string>& computed) {
A::print_(computed);
if(computed.count("C") > 0) return;
computed.insert("C");
std::cout << "c: " << c << std::endl;
}
};
struct D : public B,public C {
int d;
D(int a_,int b_,int c_,int d_) : A(a_), B(a_,b_), C(a_,c_), d(d_) {}
virtual void print_(std::set <std::string>& computed) {
B::print_(computed);
C::print_(computed);
if(computed.count("D") > 0) return;
computed.insert("D");
std::cout << "d: " << d << std::endl;
}
};
int main() {
D d(1,2,3,4);
d.print();
}
无论如何,我会将此问题标记为已解决。 不过,我总是想听到更多的答案。
我的方法会结合你提到的那些。 我会让虚拟方法做一些不同的事情:
class A
{
public:
virtual void getInfo(std::map<std::string,std::string>& output)
{
if(output.count("A") == 0)
{
output["A"] = "a: 1";
}
}
void print()
{
std::map<std::string,std::string> m;
getInfo(m); //virtual method (most derived will be called)
std::map<std::string,std::string>::iterator iter = m.begin();
for(; iter!=m.end(); ++iter)
{
std::cout<<iter->second();
}
}
};
class B : public A
{
virtual void getInfo(std::map<std::string,std::string>& output)
{
A::getInfo(output);
if(output.count("B") == 0)
{
output["B"] = "b: 2";
}
}
};
print
现在是一种非虚拟方法,它使用 getInfo
填充容器,然后迭代它显示/保存输出。 因此,在写入字符串并将其添加到容器之前,每个类都可以检查以确保容器尚未包含该继承链级别的所需输出。
我会在 A 结构中添加一个私有标志(如果菱形超出一个级别,则添加到 B 和 C)并检查它并将其标记为已经遍历。这也将有助于更复杂的(嵌套)菱形图案。
喜欢这个:
struct A {
int a;
A(int a_) : a(a_) {traversed = false;}
virtual void print() {
if (traversed) return;
std::cout << "a: " << a << std::endl;
traversed = true;
}
private:
bool traversed;
};
类构造虚拟基(最派生的,D
),所以我会确保只有一个类打印A
对象,并且像它的构造一样,首先让它发生(如果你将对象写入磁盘,可能很重要。
您可以将void*
参数添加到A
的构造函数中,并将其存储在 A
的成员中。每个派生类都将虚拟基构造为 A(a, this)
。
向A
添加新的成员函数,do_print(void*)
并让每个派生类调用do_print(this)
而不是A::print()
。do_print(void*)
函数将其参数与传递给 A
ctor 的存储void*
进行比较,并且仅在相同时才打印。这依赖于每个具有不同地址的派生类,如果所有类都是非空的,则为 true,但如果该假设成立,则确保只有派生最多的对象打印虚拟基础。
- 在没有太多条件句的情况下,我如何避免被零除
- 为什么在没有显式默认构造函数的情况下,将另一个结构封装在联合中作为成员的结构不能编译
- 在未初始化映射的情况下,将值插入到映射的映射中
- 是默认情况下分配给char数组常量的值
- 为什么我不能在不创建字符串变量的情况下使用函数的字符串输出
- 如何在不产生任何垃圾的情况下获得C中的像素
- 在已经使用Git的情况下减少编译时间
- 为什么在Windows上的VS 2019和Clang 9中"size_t"在没有标题的情况下工作
- 如何在没有信号的情况下从C++执行QML插槽
- 如何在不知道向量大小的情况下输入向量内部的向量?
- 为什么在某些情况下不写入此文件?
- 为什么Mat类的两个对象可以在不重载运算符+的情况下添加
- 在没有Xcode的情况下在Mac捆绑包中嵌入框架
- UE4-如何在给定4个屏幕坐标的情况下缩放纹理或材质
- 为什么需要复制构造函数,在哪些情况下它们非常有用
- 在C++中如何在没有pow的情况下进行基础计算
- 防止在存在菱形继承的情况下进行冗余函数调用
- C++11 在特定情况下避免冗余返回类型
- 如何从客户端找出TCP服务器的状态(在冗余服务器的情况下)?
- 如何识别冗余库从Makefile?有没有什么工具可以分析图书馆的使用情况?