C++ 菱形问题 - 如何只调用一次基方法
C++ diamond problem - How to call base method only once
我在 C++ 中使用多重继承并通过显式调用基方法来扩展基方法。假定以下层次结构:
Creature
/ \
Swimmer Flier
\ /
Duck
对应
class Creature
{
public:
virtual void print()
{
std::cout << "I'm a creature" << std::endl;
}
};
class Swimmer : public virtual Creature
{
public:
void print()
{
Creature::print();
std::cout << "I can swim" << std::endl;
}
};
class Flier : public virtual Creature
{
public:
void print()
{
Creature::print();
std::cout << "I can fly" << std::endl;
}
};
class Duck : public Flier, public Swimmer
{
public:
void print()
{
Flier::print();
Swimmer::print();
std::cout << "I'm a duck" << std::endl;
}
};
现在出现了一个问题——调用鸭子的print
方法会调用它各自的基本方法,所有这些方法又会调用Creature::print()
方法,所以它最终被调用了两次-
I'm a creature
I can fly
I'm a creature
I can swim
I'm a duck
我想找到一种方法来确保基方法只被调用一次。类似于虚拟继承的工作方式(在第一次调用时调用基本构造函数,然后仅在其他派生的连续调用中为它分配一个指针类)。
是否有一些内置的方法可以做到这一点,还是我们需要自己实现一个?
如果是这样,您将如何处理?
这个问题不是针对打印的。我想知道是否有一种机制可以扩展基本方法和功能,同时保持调用顺序并避免菱形问题。
我现在明白最突出的解决方案是添加辅助方法,但我只是想知道是否有 "cleaner" 方法。
我们可以让基础 class 跟踪属性:
#include <iostream>
#include <string>
#include <vector>
using namespace std::string_literals;
class Creature
{
public:
std::string const attribute{"I'm a creature"s};
std::vector<std::string> attributes{attribute};
virtual void print()
{
for (auto& i : attributes)
std::cout << i << std::endl;
}
};
class Swimmer : public virtual Creature
{
public:
Swimmer() { attributes.push_back(attribute); }
std::string const attribute{"I can swim"s};
};
class Flier : public virtual Creature
{
public:
Flier() { attributes.push_back(attribute); }
std::string const attribute{"I can fly"s};
};
class Duck : public Flier, public Swimmer
{
public:
Duck() { attributes.push_back(attribute); }
std::string const attribute{"I'm a duck"s};
};
int main()
{
Duck d;
d.print();
}
同样,如果我们追求的不仅仅是打印,而是函数调用,那么我们可以让基础 class 跟踪函数:
#include <iostream>
#include <functional>
#include <vector>
class Creature
{
public:
std::vector<std::function<void()>> print_functions{[this] {Creature::print_this(); }};
virtual void print_this()
{
std::cout << "I'm a creature" << std::endl;
}
void print()
{
for (auto& f : print_functions)
f();
}
};
class Swimmer : public virtual Creature
{
public:
Swimmer() { print_functions.push_back([this] {Swimmer::print_this(); }); }
void print_this()
{
std::cout << "I can swim" << std::endl;
}
};
class Flier : public virtual Creature
{
public:
Flier() { print_functions.push_back([this] {Flier::print_this(); }); }
void print_this()
{
std::cout << "I can fly" << std::endl;
}
};
class Duck : public Flier, public Swimmer
{
public:
Duck() { print_functions.push_back([this] {Duck::print_this(); }); }
void print_this()
{
std::cout << "I'm a duck" << std::endl;
}
};
int main()
{
Duck d;
d.print();
}
您对 print
方法的显式调用构成了问题的关键。
一种解决方法是放弃 print
调用,并用 say
替换它们
void queue(std::set<std::string>& data)
然后您将打印消息累积到 set
中。那么层次结构中的那些函数被多次调用并不重要。
然后您在 Creature
.
中用一个方法实现集合的打印
如果您想保留打印顺序,则需要将 set
替换为另一个遵守插入顺序并拒绝重复的容器。
很可能这是一个 XY 问题。但是...只是不要调用它两次。
#include <iostream>
class Creature
{
public:
virtual void identify()
{
std::cout << "I'm a creature" << std::endl;
}
};
class Swimmer : public virtual Creature
{
public:
virtual void identify() override
{
Creature::identify();
tell_ability();
std::cout << "I'm a swimmer\n";
}
virtual void tell_ability()
{
std::cout << "I can swim\n";
}
};
class Flier : public virtual Creature
{
public:
virtual void identify() override
{
Creature::identify();
tell_ability();
std::cout << "I'm a flier\n";
}
virtual void tell_ability()
{
std::cout << "I can fly\n";
}
};
class Duck : public Flier, public Swimmer
{
public:
virtual void tell_ability() override
{
Flier::tell_ability();
Swimmer::tell_ability();
}
virtual void identify() override
{
Creature::identify();
tell_ability();
std::cout << "I'm a duck\n";
}
};
int main()
{
Creature c;
c.identify();
std::cout << "------------------\n";
Swimmer s;
s.identify();
std::cout << "------------------\n";
Flier f;
f.identify();
std::cout << "------------------\n";
Duck d;
d.identify();
std::cout << "------------------\n";
}
输出:
I'm a creature
------------------
I'm a creature
I can swim
I'm a swimmer
------------------
I'm a creature
I can fly
I'm a flier
------------------
I'm a creature
I can fly
I can swim
I'm a duck
------------------
一个简单的方法是创建一堆助手 classes 来模仿你的主层次结构的继承结构,并在它们的构造函数中完成所有打印。
struct CreaturePrinter {
CreaturePrinter() {
std::cout << "I'm a creature\n";
}
};
struct FlierPrinter: virtual CreaturePrinter ...
struct SwimmerPrinter: virtual CreaturePrinter ...
struct DuckPrinter: FlierPrinter, SwimmerPrinter ...
然后主层次结构中的每个打印方法只创建相应的助手 class。没有手动链接。
为了便于维护,您可以让每台打印机 class 嵌套在其对应的主要 class.
中
自然地,在大多数现实世界的情况下,您希望将对主对象的引用作为参数传递给其助手的构造函数。
如果您想要中间 class 方法,请不要调用基础 class 方法。最简单最简单的方法就是提取额外的方法,然后重新实现Print
很容易。
class Creature
{
public:
virtual void print()
{
std::cout << "I'm a creature" << std::endl;
}
};
class Swimmer : public virtual Creature
{
public:
void print()
{
Creature::print();
detailPrint();
}
void detailPrint()
{
std::cout << "I can swim" << std::endl;
}
};
class Flier : public virtual Creature
{
public:
void print()
{
Creature::print();
detailPrint();
}
void detailPrint()
{
std::cout << "I can fly" << std::endl;
}
};
class Duck : public Flier, public Swimmer
{
public:
void print()
{
Creature::Print();
Flier::detailPrint();
Swimmer::detailPrint();
detailPrint();
}
void detailPrint()
{
std::cout << "I'm a duck" << std::endl;
}
};
没有详细说明您的实际问题是什么,很难想出更好的解决方案。
使用:
template<typename Base, typename Derived>
bool is_dominant_descendant(Derived * x) {
return std::abs(
std::distance(
static_cast<char*>(static_cast<void*>(x)),
static_cast<char*>(static_cast<void*>(dynamic_cast<Base*>(x)))
)
) <= sizeof(Derived);
};
class Creature
{
public:
virtual void print()
{
std::cout << "I'm a creature" << std::endl;
}
};
class Walker : public virtual Creature
{
public:
void print()
{
if (is_dominant_descendant<Creature>(this))
Creature::print();
std::cout << "I can walk" << std::endl;
}
};
class Swimmer : public virtual Creature
{
public:
void print()
{
if (is_dominant_descendant<Creature>(this))
Creature::print();
std::cout << "I can swim" << std::endl;
}
};
class Flier : public virtual Creature
{
public:
void print()
{
if (is_dominant_descendant<Creature>(this))
Creature::print();
std::cout << "I can fly" << std::endl;
}
};
class Duck : public Flier, public Swimmer, public Walker
{
public:
void print()
{
Walker::print();
Swimmer::print();
Flier::print();
std::cout << "I'm a duck" << std::endl;
}
};
对于 Visual Studio 2015,输出是:
I'm a creature
I can walk
I can swim
I can fly
I'm a duck
但是is_dominant_descendant
没有可移植的定义。我希望这是一个标准概念。
您要求在函数级别上进行继承之类的操作,自动调用继承的函数并仅添加更多代码。您还希望它以虚拟方式完成,就像 class 继承一样。伪语法:
class Swimmer : public virtual Creature
{
public:
// Virtually inherit from Creature::print and extend it by another line of code
void print() : virtual Creature::print()
{
std::cout << "I can swim" << std::endl;
}
};
class Flier : public virtual Creature
{
public:
// Virtually inherit from Creature::print and extend it by another line of code
void print() : virtual Creature::print()
{
std::cout << "I can fly" << std::endl;
}
};
class Duck : public Flier, public Swimmer
{
public:
// Inherit from both prints. As they were created using "virtual function inheritance",
// this will "mix" them just like in virtual class inheritance
void print() : Flier::print(), Swimmer::print()
{
std::cout << "I'm a duck" << std::endl;
}
};
所以你问题的答案
Is there some built-in way to do this?
是否。 C++ 中不存在这样的东西。另外,我不知道有任何其他语言有类似的东西。但这是一个有趣的想法...
我在 C++ 中使用多重继承并通过显式调用基方法来扩展基方法。假定以下层次结构:
Creature
/ \
Swimmer Flier
\ /
Duck
对应
class Creature
{
public:
virtual void print()
{
std::cout << "I'm a creature" << std::endl;
}
};
class Swimmer : public virtual Creature
{
public:
void print()
{
Creature::print();
std::cout << "I can swim" << std::endl;
}
};
class Flier : public virtual Creature
{
public:
void print()
{
Creature::print();
std::cout << "I can fly" << std::endl;
}
};
class Duck : public Flier, public Swimmer
{
public:
void print()
{
Flier::print();
Swimmer::print();
std::cout << "I'm a duck" << std::endl;
}
};
现在出现了一个问题——调用鸭子的print
方法会调用它各自的基本方法,所有这些方法又会调用Creature::print()
方法,所以它最终被调用了两次-
I'm a creature
I can fly
I'm a creature
I can swim
I'm a duck
我想找到一种方法来确保基方法只被调用一次。类似于虚拟继承的工作方式(在第一次调用时调用基本构造函数,然后仅在其他派生的连续调用中为它分配一个指针类)。
是否有一些内置的方法可以做到这一点,还是我们需要自己实现一个?
如果是这样,您将如何处理?
这个问题不是针对打印的。我想知道是否有一种机制可以扩展基本方法和功能,同时保持调用顺序并避免菱形问题。
我现在明白最突出的解决方案是添加辅助方法,但我只是想知道是否有 "cleaner" 方法。
我们可以让基础 class 跟踪属性:
#include <iostream>
#include <string>
#include <vector>
using namespace std::string_literals;
class Creature
{
public:
std::string const attribute{"I'm a creature"s};
std::vector<std::string> attributes{attribute};
virtual void print()
{
for (auto& i : attributes)
std::cout << i << std::endl;
}
};
class Swimmer : public virtual Creature
{
public:
Swimmer() { attributes.push_back(attribute); }
std::string const attribute{"I can swim"s};
};
class Flier : public virtual Creature
{
public:
Flier() { attributes.push_back(attribute); }
std::string const attribute{"I can fly"s};
};
class Duck : public Flier, public Swimmer
{
public:
Duck() { attributes.push_back(attribute); }
std::string const attribute{"I'm a duck"s};
};
int main()
{
Duck d;
d.print();
}
同样,如果我们追求的不仅仅是打印,而是函数调用,那么我们可以让基础 class 跟踪函数:
#include <iostream>
#include <functional>
#include <vector>
class Creature
{
public:
std::vector<std::function<void()>> print_functions{[this] {Creature::print_this(); }};
virtual void print_this()
{
std::cout << "I'm a creature" << std::endl;
}
void print()
{
for (auto& f : print_functions)
f();
}
};
class Swimmer : public virtual Creature
{
public:
Swimmer() { print_functions.push_back([this] {Swimmer::print_this(); }); }
void print_this()
{
std::cout << "I can swim" << std::endl;
}
};
class Flier : public virtual Creature
{
public:
Flier() { print_functions.push_back([this] {Flier::print_this(); }); }
void print_this()
{
std::cout << "I can fly" << std::endl;
}
};
class Duck : public Flier, public Swimmer
{
public:
Duck() { print_functions.push_back([this] {Duck::print_this(); }); }
void print_this()
{
std::cout << "I'm a duck" << std::endl;
}
};
int main()
{
Duck d;
d.print();
}
您对 print
方法的显式调用构成了问题的关键。
一种解决方法是放弃 print
调用,并用 say
void queue(std::set<std::string>& data)
然后您将打印消息累积到 set
中。那么层次结构中的那些函数被多次调用并不重要。
然后您在 Creature
.
如果您想保留打印顺序,则需要将 set
替换为另一个遵守插入顺序并拒绝重复的容器。
很可能这是一个 XY 问题。但是...只是不要调用它两次。
#include <iostream>
class Creature
{
public:
virtual void identify()
{
std::cout << "I'm a creature" << std::endl;
}
};
class Swimmer : public virtual Creature
{
public:
virtual void identify() override
{
Creature::identify();
tell_ability();
std::cout << "I'm a swimmer\n";
}
virtual void tell_ability()
{
std::cout << "I can swim\n";
}
};
class Flier : public virtual Creature
{
public:
virtual void identify() override
{
Creature::identify();
tell_ability();
std::cout << "I'm a flier\n";
}
virtual void tell_ability()
{
std::cout << "I can fly\n";
}
};
class Duck : public Flier, public Swimmer
{
public:
virtual void tell_ability() override
{
Flier::tell_ability();
Swimmer::tell_ability();
}
virtual void identify() override
{
Creature::identify();
tell_ability();
std::cout << "I'm a duck\n";
}
};
int main()
{
Creature c;
c.identify();
std::cout << "------------------\n";
Swimmer s;
s.identify();
std::cout << "------------------\n";
Flier f;
f.identify();
std::cout << "------------------\n";
Duck d;
d.identify();
std::cout << "------------------\n";
}
输出:
I'm a creature
------------------
I'm a creature
I can swim
I'm a swimmer
------------------
I'm a creature
I can fly
I'm a flier
------------------
I'm a creature
I can fly
I can swim
I'm a duck
------------------
一个简单的方法是创建一堆助手 classes 来模仿你的主层次结构的继承结构,并在它们的构造函数中完成所有打印。
struct CreaturePrinter {
CreaturePrinter() {
std::cout << "I'm a creature\n";
}
};
struct FlierPrinter: virtual CreaturePrinter ...
struct SwimmerPrinter: virtual CreaturePrinter ...
struct DuckPrinter: FlierPrinter, SwimmerPrinter ...
然后主层次结构中的每个打印方法只创建相应的助手 class。没有手动链接。
为了便于维护,您可以让每台打印机 class 嵌套在其对应的主要 class.
中自然地,在大多数现实世界的情况下,您希望将对主对象的引用作为参数传递给其助手的构造函数。
如果您想要中间 class 方法,请不要调用基础 class 方法。最简单最简单的方法就是提取额外的方法,然后重新实现Print
很容易。
class Creature
{
public:
virtual void print()
{
std::cout << "I'm a creature" << std::endl;
}
};
class Swimmer : public virtual Creature
{
public:
void print()
{
Creature::print();
detailPrint();
}
void detailPrint()
{
std::cout << "I can swim" << std::endl;
}
};
class Flier : public virtual Creature
{
public:
void print()
{
Creature::print();
detailPrint();
}
void detailPrint()
{
std::cout << "I can fly" << std::endl;
}
};
class Duck : public Flier, public Swimmer
{
public:
void print()
{
Creature::Print();
Flier::detailPrint();
Swimmer::detailPrint();
detailPrint();
}
void detailPrint()
{
std::cout << "I'm a duck" << std::endl;
}
};
没有详细说明您的实际问题是什么,很难想出更好的解决方案。
使用:
template<typename Base, typename Derived>
bool is_dominant_descendant(Derived * x) {
return std::abs(
std::distance(
static_cast<char*>(static_cast<void*>(x)),
static_cast<char*>(static_cast<void*>(dynamic_cast<Base*>(x)))
)
) <= sizeof(Derived);
};
class Creature
{
public:
virtual void print()
{
std::cout << "I'm a creature" << std::endl;
}
};
class Walker : public virtual Creature
{
public:
void print()
{
if (is_dominant_descendant<Creature>(this))
Creature::print();
std::cout << "I can walk" << std::endl;
}
};
class Swimmer : public virtual Creature
{
public:
void print()
{
if (is_dominant_descendant<Creature>(this))
Creature::print();
std::cout << "I can swim" << std::endl;
}
};
class Flier : public virtual Creature
{
public:
void print()
{
if (is_dominant_descendant<Creature>(this))
Creature::print();
std::cout << "I can fly" << std::endl;
}
};
class Duck : public Flier, public Swimmer, public Walker
{
public:
void print()
{
Walker::print();
Swimmer::print();
Flier::print();
std::cout << "I'm a duck" << std::endl;
}
};
对于 Visual Studio 2015,输出是:
I'm a creature
I can walk
I can swim
I can fly
I'm a duck
但是is_dominant_descendant
没有可移植的定义。我希望这是一个标准概念。
您要求在函数级别上进行继承之类的操作,自动调用继承的函数并仅添加更多代码。您还希望它以虚拟方式完成,就像 class 继承一样。伪语法:
class Swimmer : public virtual Creature
{
public:
// Virtually inherit from Creature::print and extend it by another line of code
void print() : virtual Creature::print()
{
std::cout << "I can swim" << std::endl;
}
};
class Flier : public virtual Creature
{
public:
// Virtually inherit from Creature::print and extend it by another line of code
void print() : virtual Creature::print()
{
std::cout << "I can fly" << std::endl;
}
};
class Duck : public Flier, public Swimmer
{
public:
// Inherit from both prints. As they were created using "virtual function inheritance",
// this will "mix" them just like in virtual class inheritance
void print() : Flier::print(), Swimmer::print()
{
std::cout << "I'm a duck" << std::endl;
}
};
所以你问题的答案
Is there some built-in way to do this?
是否。 C++ 中不存在这样的东西。另外,我不知道有任何其他语言有类似的东西。但这是一个有趣的想法...