开发者

Proper use of of typedef in C++

I have coworkers who occasionally use typedef to avoid typing. For example:

typedef std::list<Foobar> FoobarList;
...
FoobarList GetFoobars();

Personally, I always hate coming across code like this, largely because it forces me to go lookup the typedef so I can tell how to use it. I also feel like this sort of thing is a potential slippery slope... If you do this, why aren't you doing it more? (pretty soon, your code开发者_开发知识库 is totally obfuscated). I found this SO question regarding this issue:

when should I use typedef in C

I have two questions: 1) am I truly alone in disliking this? 2) If the vast majority of people think this sort of typedef use is OK, what criteria do you use to determine whether to typedef a type?


The two big arguments for this type of typedef are the reduced typing, which you've already mentioned, and the ease of changing over to a new type of container. A FoobarList could be backed by a vector or a list or a deque and switching would often just require changing a typedef.

Your dislike of them when it comes to looking them up, is quite reduced when dealing with IDEs, since I can just hover over the type name, and the IDE tells me what it's defined as.

The more useful situations are when you have nested containers - you can give the names some semantic meaning without having to define entire classes:

typedef std::list<Foobar> FoobarList;
typedef std::map <string, FoobarList> GizmosToFoobarsMap;

You can also save a LOT of typing when dealing with iterators of these types (although that's reduced now that C++0x has auto.)


typedefs are essential to STL and template programming in general. Just look at how iterator traits work:

template <class Iterator>
struct iterator_traits {
  typedef typename Iterator::iterator_category iterator_category;
  typedef typename Iterator::value_type        value_type;
  typedef typename Iterator::difference_type   difference_type;
  typedef typename Iterator::pointer           pointer;
  typedef typename Iterator::reference         reference;
};

template <class T>
struct iterator_traits<T*> {
  typedef random_access_iterator_tag iterator_category;
  typedef T                          value_type;
  typedef ptrdiff_t                  difference_type;
  typedef T*                         pointer;
  typedef T&                         reference;
};

As for "shortcut" usage of typedefs - I think it's perfectly fine when localized in an implementation file. I have the same rule here as for using namespace - if it saves me typing and doesn't confuse others, go for it.


I like typedefs like that because they improve readability. Consider having std::vector<std::string>::const_iterator all over the place, or even std::map<std::string, std::vector<std::string> >::iterator. Half your code ends up just describing types, and a bunch of typedefs simplifies that a lot. I don't think it's too much of a hurdle to understanding either - IDEs can usually tell you the type if you hover the mouse over it or go-to-definition, and working with code usually requires you understand things like this anyway.


I like that typedef because it allows to change the type of the container later (say, profiling showed std::deque to be faster than std::list) without changing all the code in question.

And in case you wonder: I don't object to the thing being called a list, even though it might be a std::deque. In this context I see "list" as a conceptual term, not a data structure. (You don't do your shopping lists as doubly linked lists, do you?)


Personally I also typedef STL stuff, the list and the iterator (Foos and FooIterator). Used everywhere makes for a very clear (imo) style that you get used to by the 2nd occurance.

Besides you can just mouse over the type to see what it really means in any real IDE (vs or eclipse), so you have all the information you need right at its point of use in addition to not having to scroll 3 screens to the right just to read the full line.

With the C++0x auto keyword this won't be needed anymore. Yes, I'm a believer in C#'s var keyword, and again you can mouse over it to see the type if you're in doubt!


I don't do that in my code either, so I guess you are not alone. To my mind it makes one more thing that the reader has to go look up. The poor code reader generally has more than enough of those already.


In your example FoobarList is no different from any other class; you'd still have to look the definition or documentation to know how to use it; and you would not use that as an argument for not using classes!

In this case once you'd determined that it was an STL std::list, you'd know how to use it or at least where to find good quality documentation; which is probably more than you can say for most classes that may be defined in your project.

That said I am ambivalent as to whether it is a good idea or not; I just think that it is mostly harmless.


I personally find that typedefs are very useful when dealing with templated code, both when writing templates, and when instantiating them.

Writing templates:

typedefs are required for template metaprogramming. For Example, removing const:

template<typename T>
struct RemoveConst
{
typedef T Type;
};

template<>
struct RemoveConst<const T>
{
typedef T Type;
};

Now, we can access the const-less type from any T by instantiating RemoveConst::Type

Instantiating templates:

Like a lot of things in programming, right tool for the right job. typedefs such as your example

typedef std::list<Foobar> FoobarList;
...
FoobarList GetFoobars();

Sound completely reasonable, mainly because the typedefed name is descriptive(a FoobarList is a List of Foobars). This is very useful particularly when dealing with STL containers, or any other template types that share the same interface. imagine the following class declaration:

class SomeClass
{
...
std::vector<int> mContainer;
};

This class is likely to iterate over the elements of Container, which would result in code similar to:

for(std::vector<int>::iterator It = mContainer.begin(); It != mContainer.end(); ++It)
{
}

Now imagine that you realize, after writing the above for-loop in 5 different metods, that you are constantly inserting in the middle of the array, and that a std::list would be much better suited for the job. In this case, you would have to go through every instance of std::vector::iterator, and change the declaration.

Instead, What you can do, is typedef the container you are using inside the class:

class SomeClass
{
typedef std::vector<int> IntContainer;
...
IntContainer mContainer;
};

This can allow you to write highly generic code, that can be very easily modifiable.

for(IntContainer::iterator It = mContainer.begin(); It != mContainer.end(); ++It)
{
}

In this case, you only need to change the typedef of IntContainer, and any instance that refers to it, is automatically changed.


You see typedefs a lot when you need to write C++ code that works across platforms, because the size of the fundamental datatypes are not fixed. For example, the Java virtual machine has typedefs for the fundamental types for different compilers.


I think the typedef is good practice in this case. While the template syntax puts the parameters in you face, they are usually the sort of things one would try to encapsulate in an object oriented environment.

typedef'ing the template performs that encapsulation.


If I want the ending type to be strongly typed, I typically prefer inheritance over typedef, e.g. class CMyMap : public CMap<int, int> {...}

This way, CMyMap is strongly typed - I just can't pass any CMap<int, int> into a method that takes a CMyMap as a parameter. typedef just tells the compiler "another name for", but class definition defines a new type.

If I defined a function void foo(const CMyMap &map);

but I used typedef to define CMyMap, e.g. typdef CMap<int, int> CMyMap; then someone could do CMap<int, int> myMap; foo(myMap); and the compiler would be quite happy.

But if I really want foo to be strongly typed and ONLY take CMyMap, then inheritance is my mechanism of choice for "typing".

BTW, CMyMap is kindof generic, but something more specific might be CPhoneNumber2Index where the phone number is represented as an integer and the index is an integer. It is obvious here that my CPhoneNumber2Index is not just a map from an integer to another integer. So foo might be string ReverseLookup(const CPhoneNumber2Index &PhoneBook, int iPhoneNumber); which shows that ReverseLookup has something to do with phone numbers, not CMap<int, int>


After some heavy messing with templates, typedef behaves like normal variable assignment...

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜