C++中什么时候应该使用typedef?

79

在我多年的C++ (MFC)编程中,我从未感到需要使用typedef,所以我不太知道它的用途。我应该在哪里使用它?是否有任何情况下优先使用typedef?还是这个关键字只用于C语言?


好奇问一下,什么是MFC? - Milan
1
@Milan https://zh.wikipedia.org/wiki/Microsoft_Foundation_Class_Library - djeidot
13个回答

91

模板元编程

typedef 对于许多模板元编程任务是必需的——每当一个类被视为“编译时类型函数”时,typedef 就被用作“编译时类型值”来获取结果类型。例如,考虑一个简单的元函数,将指针类型转换为其基本类型:

template<typename T>
struct strip_pointer_from;

template<typename T>
struct strip_pointer_from<T*> {   // Partial specialisation for pointer types
    typedef T type;
};

示例:类型表达式strip_pointer_from<double*>::type的计算结果为double。请注意,模板元编程在库开发以外并不常用。

简化函数指针类型

typedef对于给复杂的函数指针类型起一个简短、明确的别名非常有帮助:

typedef int (*my_callback_function_type)(int, double, std::string);

void RegisterCallback(my_callback_function_type fn) {
    ...
}

2
必要吗?能举个例子吗?我想不出有任何必要的情况。 - jalf
13
在C++11中,“using a = b”语法的添加使“typedef”关键字大多成为了过去,这是件好事情,因为typedef总是令人困惑,并且与#define不一致(现在我永远不会意外地颠倒它们,因为它们的变量赋值顺序相同)。 - Dwayne Robinson

40

在Bjarne的书中,他提到你可以使用typedef来处理不同整数大小的系统之间的可移植性问题。(这是一个意译)

在一个 sizeof(int) 为4的机器上,你可以这样做

typedef int int32;

那么在你的代码中到处使用 int32。 当你迁移到一个C++实现,其中sizeof(int)为2时,你可以只更改 typedef

typedef long int32;

并且您的程序仍将在新实现上正常工作。


13
你肯定会使用 <stdint.h> 中的 uint32_t 类型,对吧? :) - Greg Rogers
只有在通常很少需要精确32位的情况下才使用。 - KeithB
7
我认为稀有程度取决于你从事的开发类型。嵌入式系统开发人员和经常处理文件格式的人是我能想到的两种情况,这些情况下你通常需要知道精确大小。 - j_random_hacker

23

使用函数指针

使用typedef隐藏函数指针声明

void (*p[10]) (void (*)() );

只有少数程序员能够知道 p 表示的是“一个由10个指向返回void类型且接受另一个不带参数且返回void类型的函数指针所组成的数组”。这种冗长的语法几乎难以理解。然而,你可以通过使用typedef声明来大大简化它。首先,按如下方式声明一个“指向不带参数且返回void类型的函数指针”的typedef:

  typedef void (*pfv)();

接下来,根据我们之前声明的typedef,声明另一个"返回void且接受pfv参数的函数指针"的typedef:


typedef void (*ptr_to_func)(pfv);
 typedef void (*pf_taking_pfv) (pfv);

现在我们已经创建了pf_taking_pfv typedef,作为一个指向“返回void并且接受pfv”的函数指针的冗长同义词,声明一个由10个这样的指针组成的数组变得非常容易:

  pf_taking_pfv p[10];

来源


18

仅提供一些关于所说的东西的例子:STL容器。

 typedef std::map<int,Froboz> tFrobozMap;
 tFrobozMap frobozzes; 
 ...
 for(tFrobozMap::iterator it=frobozzes.begin(); it!=map.end(); ++it)
 {
     ...
 }

即使使用typedef也并非不寻常

typedef tFrobozMap::iterator tFrobozMapIter;
typedef tFrobozMap::const_iterator tFrobozMapCIter;

另一个例子:使用共享指针:

class Froboz;
typedef boost::shared_ptr<Froboz> FrobozPtr;

[更新] 根据评论 - 放在哪里?

最后一个例子 - 使用 shared_ptr - 是很容易的:它们是真正的头文件材料 - 或者至少是前向声明头文件。你无论如何都需要 shared_ptr 的前向声明,并且其声明的优点之一是可以安全地使用前向声明。

换句话说:如果有一个 shared_ptr,你可能只应该通过 shared_ptr 使用该类型,因此将声明分开并没有太多意义。

(是的,xyzfwd.h 很让人痛苦。我只会在热点区域中使用它们 - 知道热点很难被识别。怪C ++编译+链接模型...)

容器 typedef 我通常在声明容器变量的位置使用 - 例如,在本地变量中本地使用,作为类成员当实际容器实例是类成员时。如果实际容器类型是实现细节,则此方法效果很好 - 不会增加其他依赖。

如果它们成为 特定 接口的一部分,则与它们一起使用的接口一起声明,例如:

// FrobozMangler.h
#include "Froboz.h"
typedef std::map<int, Froboz> tFrobozMap;
void Mangle(tFrobozMap const & frobozzes); 

当类型是不同接口之间的绑定元素时,这会变得棘手 - 即多个头需要相同的类型。一些解决方案:

  • 与包含的类型一起声明 (适用于频繁使用此类型的容器)
  • 将它们移动到单独的头文件中
  • 移动到单独的头文件中,并使其成为数据类,其中实际容器再次成为实现细节

我同意后两者不是那么好,只有在遇到问题时才会使用它们(不是主动使用)。


你能讨论一下关于头文件的最佳实践吗?选项似乎是将typedef放在Froboz.h中,这会创建头文件依赖和长时间的构建时间;按照Effective C++的建议,在Frobozfwd.h中放置typedefs,这似乎对可维护性来说很麻烦(每个东西都需要两个头文件);或者将typedefs放在FroCommon.h中,这会破坏可重用性。有更好的方法吗? - Rob Napier
1
谢谢。我在这里放了一个更长的问题版本:https://dev59.com/13E95IYBdhLWcg3wV8W_。我担心到目前为止我得出了相同的结论,即没有真正可靠的答案,这意味着很难制定一个团队中每个人都可以遵循和依赖的规则。“对于这个头文件,你需要使用fwd版本,但是*这个*头文件你只需包含基本头文件,而*这个*相关的内容在common.h中定义...”任何人如何编写可维护和可重用的C++代码?(ObjC已经宠坏了我... :D) - Rob Napier

6

typedef 在很多情况下都非常有用。

基本上,它允许您为类型创建一个别名。当/如果您必须更改类型时,代码的其余部分可能不会改变(当然这取决于代码)。 例如,假设您想对 C++ 向量进行迭代。

vector<int> v;

...

for(vector<int>::const_iterator i = v->begin(); i != v.end(); i++) {

// Stuff here

}

未来你可能会考虑使用列表替换向量,因为你需要在其上执行的操作类型。如果没有typedef,你就必须更改代码中所有出现的向量。 但是,如果你像这样编写:

typedef vector<int> my_vect;

my_vect v;

...

for(my_vect::const_iterator i = v->begin(); i != v.end(); i++) {

// Stuff here

}

现在,您只需要更改一行代码(即从“typedef vector<int> my_vect”更改为“typedef list<int> my_vect”),然后一切都可以正常工作。

当您需要编写非常长且难以阅读的复杂数据结构时,“typedef”还可以节省您的时间。


1
这并不是使用typedef的一个很好的理由:你应该为此使用接口类型(如果你喜欢,也可以使用抽象数据类型)。这就是你需要添加“依赖于代码”的原因。应该是代码依赖于类型 :) - xtofl
C++0x即将到来!AWW-TO!AWW-TO!AWW-TO! - David Thornley
3
typedef和接口类型都可以有效解决这个特定问题。接口类型更通用,但也更加繁重。此外,正确使用接口类型意味着所有调用都将是虚拟的--这对于迭代器的前进/反向引用来说是一个沉重的代价。 - j_random_hacker

5

使用typedef的一个好处是,如果某个东西的类型可能会改变,你可以使用它。例如,假设目前使用16位整数对某个数据集进行索引是可以的,因为在可预见的未来,您将拥有少于65535个项目,并且空间限制非常重要,或者需要良好的缓存性能。但是,如果万一您需要在包含超过65535个项目的数据集上使用程序,您希望能够轻松地切换到更广的整数。使用typedef,您只需要在一个地方进行更改。


1
如果我想从int更改为unsigned long怎么办?我必须检查所有源代码是否存在溢出等问题... -> 不是使用typedef的好理由!而是应该使用包装器接口。 - xtofl
或者给typedef一个合理的名称,表明可以依赖哪些属性(如大小和符号),然后不要以破坏这些属性的方式更改它。 stdint有一些很好的模型,例如int_fast *和int_least *。那里不需要一个大接口。 - Steve Jessop
如果你担心溢出问题,那么你已经在使用numeric_limits<my_int>进行检查了,当你改变typedef定义的my_int时,这些检查依旧是正确的。 - j_random_hacker
如果您只使用int进行索引,那么sizeof(int)通常对应于处理器的位数,并且是可索引内存量的限制。因此,如果您可以使用int,您永远不会遇到这种情况。 - Joseph Garvin

5

typedef不仅允许为复杂类型创建别名,还提供了一个自然的地方来记录类型。我有时用它来进行文档编写。

有时候我会使用一个字节数组。现在,字节数组可能意味着很多东西。typedef使定义我的字节数组为“hash32”或“fileContent”变得更加方便,以使我的代码更易读。


2

typedef的现实世界应用:

  • providing friendly aliases for long-winded templated types
  • providing friendly aliases for function pointer types
  • providing local labels for types, e.g.:

    template<class _T> class A
    {
        typedef _T T;
    };
    
    template<class _T> class B
    {
        void doStuff( _T::T _value );
    };
    

我认为那不会编译。你可能是指“void doStuff(typename A<_T>::T _value);”吗?(你需要在其中加入typename关键字,否则编译器会将A<_T>::T解释为成员变量名。) - j_random_hacker

2

有另一种使用typedef的用例是当我们想要启用一种容器无关代码(但不完全如此!)

假设您有一个类:

Class CustomerList{

public:
    //some function
private:
    typedef list<Customer> CustomerContainer;
    typedef CustomerContainer::iterator Cciterator;
};

上面的代码使用typedef封装了内部容器的实现,即使将来列表容器需要更改为vector或deque,CustomerList类的用户也不需要担心确切的容器实现。
因此,typedef封装并在一定程度上帮助我们编写容器无关的代码。

0

每当它使源代码更清晰或更易于阅读时。

我在C#中使用了一种类似于typedef的东西来处理泛型/模板。一个"NodeMapping"比很多"Dictionary<string, XmlNode>"更易于阅读、使用和理解。在我看来,所以我建议在模板中使用它。


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