C++语言设计导致的特定模式

10
我花了很长时间才意识到,下面这些变量的存在在堆栈上,并且在超出范围时调用其析构函数,是多么重要和微妙。
1)存在于堆栈上。 2)在超出作用域时调用其析构函数。
这两个特点使得像以下这些东西成为可能:
A)RAII B)引用计数GC
有趣的是,(1) 和 (2) 在“较低级”语言(如C/Assembly)中不可用;在“较高级”语言(如Ruby/Python/Java)中也不可用(因为 GC 防止对象可预测地销毁)。
我很好奇——你是否知道其他非常特定于 C++ 的技术,由于语言设计选择而导致?
谢谢!
编辑:如果你的答案是“这在C++和其他语言中都适用”,那也可以。我想学习的东西类似于:
通过选择不具有某些功能(例如GC),我们获得了其他功能(例如 RAII+可预测的对象销毁)。在哪些领域中,C++ 通过选择不具有其他“高级”语言具有的功能,实现了这些高级语言无法表达的模式?

1
是的,通过RAII(带或不带引用计数)进行资源管理并不真正意义上属于GC,它更加通用。 - anon
1
你也可以在VBScript中这样做,因为GC运行是可预测的 - 你知道一旦对象超出范围,析构函数就会被执行。 - Thom Smith
1
他(或她)的意思是,你可以使用引用计数在C++中实现自己的简单GC...并不是说C++有GC。 - Polaris878
1
“C++ 能够实现那些高级语言不能表达的模式。” - 实际上,这更像是因为 C++ 比那些高级语言更加危险和不够表达而需要大量常见做法。 - BlueRaja - Danny Pflughoeft
2
C++的表达能力不够?那很有趣,我喜欢C++,因为很少有语言能与之相比。 - Georg Fritzsche
显示剩余5条评论
7个回答

7

3

我非常喜欢trait classes。虽然不是C ++特有的(其他语言如Scala也有),但它允许您适应对象,基本上指定类型应支持的一组操作。想象一下,您需要一个“哈希器”,就像。哈希为某些类型定义,但不适用于其他类型。您如何创建一个具有所有所需类型的哈希定义的类?您可以声明一个这样的类:

template < typename T>
struct hashing_tratis
{
    typedef std::tr1::hash<T> hashing_type;
};

也就是说,您希望使用已定义了正确的hashing_type的类。然而,如果对于myType未定义hash,则可以编写以下内容:

template <>
struct hashing_traits<myType>
{
    typedef class_that_knows_how_to_hash_myType hashing_type;
};

假设您需要一种方法来哈希程序中使用的任何类型(包括myType)。 您可以通过创建哈希特征(hashing trait)来编写“通用”哈希器:

template <typename T>
struct something {
    typename hashing_traits<T>::hashing_type hasher;
    .... // here hasher is defined for all your relevant types, and knows how to hash them

支持反射的语言可以在不使用繁琐的traits类的情况下完成此操作。它们只需询问类是否支持接口并调用即可。在支持一阶函数的语言中也可以实现相同的功能。 - jmucchiello
1
jmucchiello:嗯,不完全是这样。例如,当您请求此对象未实现的类或接口时会发生什么?您必须“搜索”适配器。 C++中的类型参数化允许使用同质接口进行此特征构建。 - Diego Sevilla

2
  • Always make the caller responsible for memory allocation/deallocation, which means that code that would look like this in Java/C#:

    MyClass doSomething(MyClass someInstance);
    

    looks like this in C/C++:

    void doSomething(MyClass* someBuffer, MyClass* someInstance);
    
  • Using destructors to close sockets, file-handles, etc.

1
或者 boost::shared_ptr<MyClass> doSomething(const MyClass& someInstance); - Alex B
1
如果你想强制调用者负责分配和释放内存,你应该使用引用:void doSomething(const myclass& in_param, myclass& out_param); 你不能调用doSomething并在某处分配out_param的内存然后再传递给它。 - jmucchiello
实际上,最好的做法是完全负责所有分配/释放你的代码应该使用的资源。你可以通过以下两种方式来实现:(1)使用智能指针,在不再使用时释放已分配的资源;(2)通过返回一个副本并使该副本在销毁时释放其资源。 - Leonardo Raele

2
这可以使用D编程语言完成,与你提到的两种语言一样,但是强大到足以实现编译时鸭子类型的模板非常有用。我经常觉得静态语言和动态语言之间的辩论在很大程度上可以通过足够好的模板系统来解决,因此即使需要在编译时静态解析类型,也不需要在开发时知道它们。C++在主流语言中开创了这个想法。D将其推进了几步。

使用模板和编译时鸭子类型,您将获得最佳效果:不必在函数或类的开发时间指定类型的灵活性,以及在编译时知道类型的安全性和性能。


这个问题特别涉及到C++。 - anon
1
@Neil:我只是指出它并不是完全特定的,尽管C++是这种技术最流行的语言。 - dsimcha

1

嗯,几乎所有的“设计模式”都与C++有着紧密的联系。你应该假设它们与“适当的设计”和“最佳实践”没有任何关系,而是与奇怪的C++怪癖有关,并仔细考虑是否真正需要它们,而不是盲目地使代码复杂化。特别是因为许多设计模式是用来修补由其他设计模式创建的问题的临时措施。这在使用任何语言时都更加适用,因为C++有很多其他语言没有的问题。

例如,单例模式就是最明显的例子。其真正原因是C++非常糟糕地实现了静态初始化。如果没有这个,你实际上必须知道如何正确地进行初始化,而大多数人确实不知道。对于大多数用途来说,单例模式的额外开销是可以接受的,但应该记住它确实有开销,并且在大多数语言中没有用处。我见过的每一个实现都会带来额外的问题。

同样适用于桥接模式,我可以看到使用单例模式,但根本没有理由使用桥接模式。这归结为“你知道自己在做什么吗?”如果是这样,你就不需要桥接模式。如果不是,你应该在尝试解决促使你使用桥接模式的问题之前学习更多知识。最重要的是,在除了C++之外的语言中,它实际上并不是很有用。它的目的是分离实现和接口,在更现代的面向对象语言中已经完成了,因为它们具有某种适当的模块。在C++中,最好使用纯虚拟接口来处理这种情况(不要认为这会导致性能下降,它比与模板相结合的桥接模式具有更好的性能)。
但这就是设计模式的问题;它根本不是一种方法论,只是一堆随意的东西。大多数人提倡它们的人并不真正理解这些随意的东西,其中大部分都不应该被称为任何带有“设计”一词的东西。它们不是设计工具,而是针对各种问题的具体解决方案。其中许多是可以避免的。
具有讽刺意味的是,Java API充满了桥接和各种其他设计模式,在Java中完全没有意义。大多数情况下,Java API很容易使用,但过于复杂的继承关系可能会非常麻烦。

为什么良好的模板桥接模式实现的性能会比基于vtable的实现差呢?此外,桥接模式不仅仅是一个模块问题,通过基于层次结构的方法隐藏复杂性是其主要价值所在,无论语言如何。 - Georg Fritzsche
1
一个额外的间接层并不会对性能产生太大影响,但它有助于养成良好的GRASP习惯。而且您并没有回答问题。 - Leonardo Raele

1

0
很少有语言像C++一样支持多重分派(其中双重分派最常见),你可以静态或动态地完成它。
通过它,您可以让实例相互交互而不必先知道或测试它们的具体类型。

我认为几乎所有的编程语言(除了像CLOS这样本身支持它的语言)都像C++一样支持它 - 你必须自己编写代码。 - anon
@Hogan:哦,我得查一下。只能动态地吗? - Georg Fritzsche
@gf:是的,请看http://blogs.msdn.com/laurionb/archive/2009/08/13/multimethods-in-c-4-0-with-dynamic.aspx。 - Hogan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接