何时使用typedef?


14

我对于是否以及何时应该在C ++中使用typedef感到有些困惑。我觉得这是可读性和清晰度之间的平衡。

这是一个没有任何typedef的代码示例:

int sum(std::vector<int>::const_iterator first, 
        std::vector<int>::const_iterator last)
{
    static std::map<std::tuple<std::vector<int>::const_iterator,
                               std::vector<int>::const_iterator>,
                    int> lookup_table;

    std::map<std::tuple<std::vector<int>::const_iterator,
                        std::vector<int>::const_iterator>, int>::iterator lookup_it =
        lookup_table.find(lookup_key);

    if (lookup_it != lookup_table.end())
        return lookup_it->second;            

    ...
}

IMO很丑。因此,我将在函数内添加一些typedef以使其看起来更好:

int sum(std::vector<int>::const_iterator first, 
        std::vector<int>::const_iterator last)
{
    typedef std::tuple<std::vector<int>::const_iterator,
                       std::vector<int>::const_iterator> Lookup_key;
    typedef std::map<Lookup_key, int> Lookup_table;

    static Lookup_table lookup_table;

    Lookup_table::iterator lookup_it = lookup_table.find(lookup_key);

    if (lookup_it != lookup_table.end())
        return lookup_it->second;            

    ...
}

代码仍然有些笨拙,但是我摆脱了大多数噩梦般的内容。但是仍然有int向量迭代器,此变体摆脱了这些迭代器:

typedef std::vector<int>::const_iterator Input_iterator;

int sum(Input_iterator first, Input_iterator last)
{
    typedef std::tuple<Input_iterator, Input_iterator> Lookup_key;
    typedef std::map<Lookup_key, int> Lookup_table;

    static Lookup_table lookup_table;

    Lookup_table::iterator lookup_it = lookup_table.find(lookup_key);

    if (lookup_it != lookup_table.end())
        return lookup_it->second;            

    ...
}

这看起来很干净,但是仍然可读吗?

什么时候应该使用typedef?我一旦做恶梦类型?一旦发生不止一次?我应该放在哪里?我应该在函数签名中使用它们还是将它们保留在实现中?


1
不是重复的,而是与我的问题有关的程序员
。stackexchange.com/

typedef Input_iterator std::vector<int>::const_iterator;是倒退
Per Johansson

1
可读性和清晰度之间有区别吗?
2012年

#define不够好。
托马斯·爱丁

Answers:


6

您的最后一个示例可读性很强,但这取决于您在哪里定义typedef。本地范围typedef(如您的第二个示例中所示)几乎总是IMVHO的胜利。

我仍然最喜欢您的第三个示例,但是您可能需要考虑名称,并为迭代器指定可以说明容器用途的名称。

另一个选择是从您的函数中制作一个模板,以便它也可以与其他容器一起使用。沿线

template <typename Input_iterator> ... sum(Input_iterator first, Input_iterator last) 

这也非常符合STL的精神。


2

可以将a typedef视为等同于函数的变量声明:它在那里,所以您...

  • ...在重用相同类型时不必重复自己(前两个示例都这样做)。
  • ...可以隐藏该类型的细节,因此它们并不总是显示出来。
  • ...确保对类型的更改反映在使用它们的每个地方。

就我个人而言,如果不得不像std::vector<int>::const_iterator反复阅读长字体名称一样,我会一头雾水。

您的第三个示例不会不必要地重复,并且最容易阅读。


1

typedef声明在本质上与封装具有相同的目的。因此,它们几乎总是最适合头文件,并遵循与类相同的命名约定,因为:

  • 如果您需要typedef,调用者也有可能,尤其是在您的示例中在参数中使用它的情况下。
  • 如果您出于某种原因需要更改类型,包括用自己的班级替换它,则只需要在一个地方进行即可。
  • 由于重复编写复杂类型,它使您不太容易出错。
  • 它隐藏了不必要的实现细节。

顺便说一句,如果进一步将其抽象化,您的记忆代码将更加干净,例如:

if (lookup_table.exists(first, last))
    return lookup_table.get(first, last);

您的建议可能看起来更清晰,但是通过两次查询却浪费了时间。
德里克·勒德贝特

是的,这是有意取舍的。但是,有多种方法可以使一次查找几乎一样干净,尤其是在您不担心线程安全的情况下。
卡尔·比勒费尔德
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.