可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm not sure how to order my functions in C++. In C, I simply placed a function that uses another function below that function, as closely as possible - that's pretty common. Like this:
void bar() { } void foo() { bar(); }
However, in C++, there are several types of functions:
- Free functions
- Private member functions
- Public member functions
- Static member functions
I'm currently making my function order dependent on how they are ordered in the .hpp file, e.g.:
class Foo_bar { public: Foo_bar(); void foo(); private: int some_member; void bar();
But now, if the constructor uses foo() or bar(), these will be below the constructor in the source file, inconsistent with my usual ordering. I could of course reorder my header to take account of that:
class Foo_bar { private: int some_member; void bar(); public: void foo(); Foo_bar();
But I think that's a mess.
Furthermore, in Java, the opposite to my first example seems to be common:
void foo() { bar(); } void bar() { }
That's probably due to the top-down thinking common in OOP, in contrast to the bottom-up thinking common in procedural/functional programming. However, with free functions that don't have prototypes, this top-down style is not possible.
Is it even possible to order functions in C++ in a consistent way?
回答1:
It's possible. You have to use forward declaration.
Declare a function before defining it, and other functions will see it without problem even if they are defined before.
So, you should be able to do this in C++:
void bar(); // forward declaration; note that function bar isn't defined yet void foo() { bar(); // foo knows that bar is declared, so it will search for bar's definition } void bar() // here bar is defined, so foo will use this when needed { }
回答2:
It's a pretty good question actually, because readability has a major impact on whoever will read the code after you.
There are 3 kinds of people who will read the code of a class:
- those who wish to uses it (and don't care much about its internals)
- those who wish to inherit from your class (and don't care much about its internals)
- those who wish to hack on your class, and thus really care of its internals
For this reason, I try to order the headers so that any user may stop once he got what he was looking for, which means:
class Foo { public: // types // static methods // methods (usually constructors, // then simple accessors, // then more complicated stuff) protected: // same pattern private: // same pattern // attributes }; // Free functions about this class // Implementation of inline / template methods
Sometimes I need to declare some types beforehand even though they are private, but this is rare. The goal of this ordering is to absolutely minimize the amount of stuff a reader has to read before he gets what he wants (and stops reading and goes back to what he was doing before having to interrupt himself to look at your code).
Then, regarding "helper" methods, it depends on the type of code:
- for template code, I use a "details" namespace, it's both clear to the reader that he should not be worried about it and it isolate the names in their own namespace so that they do not pop up in code completion tools
- for regular code, I use an anonymous namespace within the source file, which is even better since then it actually generates invisible symbols and I don't run the risk of violating ODR.
If some code may require a lot of helpers, I tend to create a dedicated header file in the source directory, giving the following structure:
include/ foo.hpp src/ fooImpl.hpp --> #include "foo.hpp" foo.cpp --> #include "fooImpl.hpp"
in order to provide a list of declarations to the reader, because it's easier to browse a list of declarations than to extract the declarations from a list of definitions, whatever the indentation and style.
And of course, always to make it easier, I always order the list of declarations and the list of definitions equally...
回答3:
You declare the class in a header file, right? And implement most of it in a separate file? If you simply implement the constructor in the implementation file and not in the header, I don't think you'll experience the problem you mentioned (because the entire header will be seen before the constructor is seen to call foo()
or bar()
.
回答4:
Ordering free functions in C++ obeys the same rules as you mentioned, but like darioo said, you can forward declare them and order the function definitions any way you want. That is also the preferred way: declare everything in header, and put ALL definitions in the source file. This is not possible for templates though, without some non-trivial and non-general anti-template workarounds.
In a class, things are usually different, because there is almost no cases where you fully implement your class in a header, thus the declarations are always read when you're defining the functions in the source file.
I usually order functions in "function", and group eg. getters and setters, constructor/destructor (if possible).
回答5:
Your concern about reordering the functions in the class definition is not correct as clarified by the following two quotes from the C++03 Standard.
$9.2/2- "A class is considered a completely-defined object type (3.9) (or complete type) at the closing } of the class-specifier. Within the class member-specification, the class is regarded as complete within function bodies, default arguments and constructor ctor-initializers (including such things in nested classes). Otherwise it is regarded as incomplete within its own class member-specification."
And
$3.4.1/8 - "A name used in the definition of a member function (9.3) of class X following the function’s declaratorid29) shall be declared in one of the following ways:
― before its use in the block in which it is used or in an enclosing block (6.3), or
― shall be a member of class X or be a member of a base class of X (10.2), or
― if X is a nested class of class Y (9.7), shall be a member of Y, or shall be a member of a base class of Y (this lookup applies in turn to Y’s enclosing classes, starting with the innermost enclosing class),30) or
― if X is a local class (9.8) or is a nested class of a local class, before the definition of class X in a block enclosing the definition of class X, or
― if X is a member of namespace N, or is a nested class of a class that is a member of N, or is a local class or a nested class within a local class of a function that is a member of N, before the member function definition, in namespace N or in one of N’s enclosing namespaces.
As a general rule, in C++, function definitions have to be visible at the point of their use. The only exception is the case of class member functions as illustrated by the above quotes.
Therefore, this means that the class member functions to be called by the constructor need not be defined before the constructor lexically.
回答6:
Personally, I like to see things that will be referred to from elsewhere (which people will need to find/read often) near the top of the file. Internals that, once stable, can hopefully be forgotten are left for later.
There's inconsistencies, though. For example, in a class, it implies putting public stuff first, private internals later. But the default visibility for a class (what you naturally get) is private, and (particularly if I have inline-style methods) I generally put any private data in front. It may even be an error for an inline-style method to reference a member variable before it has been defined - sorry, I'm suffering a temporary memory issue.
But basically, the main thing is to put things together than are similar or logically related. A begin method will be adjacent to an end method, a Step_To_Next method adjacent to a Step_To_Prev method, etc. Grouping be similar purposes, similar parameters, and commonly being used together are all good.
What calls what is mostly an implementation detail, so not something you should necessarily emphasise in header files that the user of your library will read - though in implementation code things may be different. As others have pointed out, forward declarations allow some freedom with this.
Most important (1) adopt a consistent style, and (2) don't worry too much about ambiguous cases.