Function Overloading
Use overloaded functions (including constructors) only in cases where input can be specified in different types that contain the same information. Do not use function overloading to simulate default function parameters.Definition:
You may write a function that takes a
const string&
and overload it with another that
takes const char*
.
class MyClass {
public:
void Analyze(const string &text);
void Analyze(const char *text, size_t textlen);
};
Pros: Overloading can make code more intuitive by allowing an identically-named function to take different arguments. It may be necessary for templatized code, and it can be convenient for Visitors.
Cons: One reason to minimize function overloading is that overloading can make it hard to tell which function is being called at a particular call site. Another one is that most people are confused by the semantics of inheritance if a deriving class overrides only some of the variants of a function. Moreover, reading client code of a library may become unnecessarily hard because of all the reasons against default function parameters.
Decision:
If you want to overload a function, consider qualifying the
name with some information about the arguments, e.g.,
AppendString()
, AppendInt()
rather
than just Append()
.
Default Arguments
We do not allow default function parameters.Pros: Often you have a function that uses lots of default values, but occasionally you want to override the defaults. Default parameters allow an easy way to do this without having to define many functions for the rare exceptions.
Cons: People often figure out how to use an API by looking at existing code that uses it. Default parameters are more difficult to maintain because copy-and-paste from previous code may not reveal all the parameters. Copy-and-pasting of code segments can cause major problems when the default arguments are not appropriate for the new code.
Decision: We require all arguments to be explicitly specified, to force programmers to consider the API and the values they are passing for each argument rather than silently accepting defaults they may not be aware of.
Variable-Length Arrays and alloca()
We do not allow variable-length arrays oralloca()
.
Pros:
Variable-length arrays have natural-looking syntax. Both
variable-length arrays and alloca()
are very
efficient.
Cons: Variable-length arrays and alloca are not part of Standard C++. More importantly, they allocate a data-dependent amount of stack space that can trigger difficult-to-find memory overwriting bugs: "It ran fine on my machine, but dies mysteriously in production".
Decision:
Use a safe allocator instead, such as
scoped_ptr
/scoped_array
.
Friends
We allow use offriend
classes and functions,
within reason.
Friends should usually be defined in the same file so that the
reader does not have to look in another file to find uses of
the private members of a class. A common use of
friend
is to have a FooBuilder
class
be a friend of Foo
so that it can construct the
inner state of Foo
correctly, without exposing
this state to the world. In some cases it may be useful to
make a unittest class a friend of the class it tests.
Friends extend, but do not break, the encapsulation boundary of a class. In some cases this is better than making a member public when you want to give only one other class access to it. However, most classes should interact with other classes solely through their public members.
Exceptions
We do not use C++ exceptions.Pros:
- Exceptions allow higher levels of an application to decide how to handle "can't happen" failures in deeply nested functions, without the obscuring and error-prone bookkeeping of error codes.
- Exceptions are used by most other modern languages. Using them in C++ would make it more consistent with Python, Java, and the C++ that others are familiar with.
- Some third-party C++ libraries use exceptions, and turning them off internally makes it harder to integrate with those libraries.
- Exceptions are the only way for a constructor to fail.
We can simulate this with a factory function or an
Init()
method, but these require heap allocation or a new "invalid" state, respectively. - Exceptions are really handy in testing frameworks.
Cons:
- When you add a
throw
statement to an existing function, you must examine all of its transitive callers. Either they must make at least the basic exception safety guarantee, or they must never catch the exception and be happy with the program terminating as a result. For instance, iff()
callsg()
callsh()
, andh
throws an exception thatf
catches,g
has to be careful or it may not clean up properly. - More generally, exceptions make the control flow of programs difficult to evaluate by looking at code: functions may return in places you don't expect. This results maintainability and debugging difficulties. You can minimize this cost via some rules on how and where exceptions can be used, but at the cost of more that a developer needs to know and understand.
- Exception safety requires both RAII and different coding practices. Lots of supporting machinery is needed to make writing correct exception-safe code easy. Further, to avoid requiring readers to understand the entire call graph, exception-safe code must isolate logic that writes to persistent state into a "commit" phase. This will have both benefits and costs (perhaps where you're forced to obfuscate code to isolate the commit). Allowing exceptions would force us to always pay those costs even when they're not worth it.
- Turning on exceptions adds data to each binary produced, increasing compile time (probably slightly) and possibly increasing address space pressure.
- The availability of exceptions may encourage developers to throw them when they are not appropriate or recover from them when it's not safe to do so. For example, invalid user input should not cause exceptions to be thrown. We would need to make the style guide even longer to document these restrictions!
Decision:
On their face, the benefits of using exceptions outweigh the costs, especially in new projects. However, for existing code, the introduction of exceptions has implications on all dependent code. If exceptions can be propagated beyond a new project, it also becomes problematic to integrate the new project into existing exception-free code. Because most existing C++ code at Google is not prepared to deal with exceptions, it is comparatively difficult to adopt new code that generates exceptions.
Given that Google's existing code is not exception-tolerant, the costs of using exceptions are somewhat greater than the costs in in a new project. The conversion process would be slow and error-prone. We don't believe that the available alternatives to exceptions, such as error codes and assertions, introduce a significant burden.
Our advice against using exceptions is not predicated on philosophical or moral grounds, but practical ones. Because we'd like to use our open-source projects at Google and it's difficult to do so if those projects use exceptions, we need to advise against exceptions in Google open-source projects as well. Things would probably be different if we had to do it all over again from scratch.
There is an exception to this rule (no pun intended) for Windows code.
Run-Time Type Information (RTTI)
We do not use Run Time Type Information (RTTI).Definition: RTTI allows a programmer to query the C++ class of an object at run time.
Pros:
It is useful in some unittests. For example, it is useful in tests of factory classes where the test has to verify that a newly created object has the expected dynamic type.
In rare circumstances, it is useful even outside of tests.
Cons: A query of type during run-time typically means a design problem. If you need to know the type of an object at runtime, that is often an indication that you should reconsider the design of your class.
Decision:
Do not use RTTI, except in unittests. If you find yourself in need of writing code that behaves differently based on the class of an object, consider one of the alternatives to querying the type.
Virtual methods are the preferred way of executing different code paths depending on a specific subclass type. This puts the work within the object itself.
If the work belongs outside the object and instead in some processing code, consider a double-dispatch solution, such as the Visitor design pattern. This allows a facility outside the object itself to determine the type of class using the built-in type system.
If you think you truly cannot use those ideas, you may use RTTI. But think twice about it. :-) Then think twice again. Do not hand-implement an RTTI-like workaround. The arguments against RTTI apply just as much to workarounds like class hierarchies with type tags.
Casting
Use C++ casts likestatic_cast<>()
. Do not use
other cast formats like int y = (int)x;
or
int y = int(x);
.
Definition: C++ introduced a different cast system from C that distinguishes the types of cast operations.
Pros:
The problem with C casts is the ambiguity of the operation;
sometimes you are doing a conversion (e.g.,
(int)3.5
) and sometimes you are doing a
cast (e.g., (int)"hello"
); C++ casts
avoid this. Additionally C++ casts are more visible when
searching for them.
Cons: The syntax is nasty.
Decision:
Do not use C-style casts. Instead, use these C++-style casts.
- Use
static_cast
as the equivalent of a C-style cast that does value conversion, or when you need to explicitly up-cast a pointer from a class to its superclass. - Use
const_cast
to remove theconst
qualifier (see const). - Use
reinterpret_cast
to do unsafe conversions of pointer types to and from integer and other pointer types. Use this only if you know what you are doing and you understand the aliasing issues. - Do not use
dynamic_cast
except in test code. If you need to know type information at runtime in this way outside of a unittest, you probably have a design flaw.延伸阅读文章来源于领测软件测试网 https://www.ltesting.net/