Effective C++ 笔记 —— Item 30: Understand the ins and outs of inlining.

When you inline a function, you may enable compilers to perform context-specific optimizations on the body of the function. Most compilers never perform such optimizations on "outlined" function calls.

The idea behind an inline function is to replace each call of that function with its code body. in statistics to see that this is likely to increase the size of your object code. On machines with limited memory, overzealous inlining can give rise to programs that are too big for the available space. Even with virtual memory, inline-induced code bloat can lead to additional paging, a reduced instruction cache hit rate, and the performance penalties that accompany these things.

On the other hand, if an inline function body is very short, the code generated for the function body may be smaller than the code generated for a function call. If that is the case, inlining the function may actually lead to smaller object code and a higher instruction cache hit rate!

Bear in mind that inline is a request to compilers, not a command. The request can be given implicitly or explicitly. The implicit way is to define a function inside a class definition:

class Person 
{
public:
    // ...
    int age() const { return theAge; } // an implicit inline request: age is defined in a class definition
    // ... 
private:
    int theAge;
};

Such functions are usually member functions, but Item 46 explains that friend functions can also be defined inside classes. When they are, they‘re also implicitly declared inline.

Inline functions must typically be in header files, because most build environments do inlining during compilation. In order to replace a function call with the body of the called function, compilers must know what the function looks like. (Some build environments can inline during linking, and a few — e.g., managed environments based on the .NET Common Language Infrastructure (CLI) — can actually inline at runtime. Such environments are the exception, however, not the rule. Inlining in most C++ programs is a compile-time activity.)

Templates are typically in header files, because compilers need to know what a template looks like in order to instantiate it when it’s used. (Again, this is not universal. Some build environments perform template instantiation during linking. However, compile-time instantiation is more common.)

Most compilers refuse to inline functions they deem too complicated (e.g., those that contain loops or are recursive), and all but the most trivial calls to virtual functions defy inlining. This latter observation shouldn’t be a surprise. virtual means "wait until runtime to figure out which function to call," and inline means "before execution, replace the call site with the called function." If compilers don't know which function will be called, you can hardly blame them for refusing to inline the function's body.

It all adds up to this: whether a given inline function is actually inlined depends on the build environment you’re using — primarily on the compiler. Fortunately, most compilers have a diagnostic level that will result in a warning (see Item 53) if they fail to inline a function you've asked them to.

Sometimes compilers generate a function body for an inline function even when they are perfectly willing to inline the function. For example, if your program takes the address of an inline function, compilers must typically generate an outlined function body for it. How can they come up with a pointer to a function that doesn’t exist? Coupled with the fact that compilers typically don’t perform inlining across calls through function pointers, this means that calls to an inline function may or may not be inlined, depending on how the calls are made:

inline void f() {...} // assume compilers are willing to inline calls to f
void (*pf )() = f; // pf points to f this call will be inlined, because it's a "normal" call
// ...
f(); // 
pf(); // this call probably won't be, because it's through a function pointer

The specter of un-inlined inline functions can haunt you even if you never use function pointers, because programmers aren’t necessarily the only ones asking for pointers to functions. Sometimes compilers generate out-of-line copies of constructors and destructors so that they can get pointers to those functions for use during construction and destruction of objects in arrays.

In fact, constructors and destructors are often worse candidates for inlining than a casual examination would indicate. For example, consider the constructor for class Derived below:

class Base 
{
public:
    // ...
private:
    std::string bm1, bm2; // base members 1 and 2
};

class Derived: public Base 
{
public:
    Derived() {} // Derived's ctor is empty — or is it?
    // ...
private:
    std::string dm1, dm2, dm3; // derived members 1–3
};

C++ makes various guarantees about things that happen when objects are created and destroyed. When you use new, for example, your dynamically created objects are automatically initialized by their constructors, and when you use delete, the corresponding destructors are invoked. When you create an object, each base class of and each data member in that object is automatically constructed, and the reverse process regarding destruction automatically occurs when an object is destroyed. If an exception is thrown during construction of an object, any parts of the object that have already been fully constructed are automatically destroyed. In all these scenarios, C++ says what must happen, but it doesn't say how. That's up to compiler implementers, but it should be clear that those things don't happen by themselves. There has to be some code in your program to make those things happen, and that code — the code written by compilers and inserted into your program during compilation — has to go somewhere. Sometimes it ends up in constructors and destructors, so we can imagine implementations generating code equivalent to the following for the allegedly empty Derived constructor above:

Derived::Derived() // conceptual implementation of "empty" Derived ctor
{
    Base::Base(); // initialize Base part
    try
    {
        // try to construct dm1
        dm1.std::string::string();
    }
    catch (...)
    {
        // if it throws, destroy base class part and propagate the exception
        Base::~Base();
        throw;
    }

    try 
    { 
        // try to construct dm2
        dm2.std::string::string(); 
    } 
    catch (...) 
    { 
        // if it throws,
        dm1.std::string::~string(); // destroy dm1,
        Base::~Base(); // destroy base class part, and
        throw; // propagate the exception
    }

    try 
    { 
        // construct dm3
        dm3.std::string::string(); 
    } 
    catch (...)
    { 
        // if it throws,
        dm2.std::string::~string(); // destroy dm2,
        dm1.std::string::~string(); // destroy dm1,
        Base::~Base(); // destroy base class part, and
        throw; // propagate the exception
    }
}

This code is unrepresentative of what real compilers emit, because real compilers deal with exceptions in more sophisticated ways. Still, this accurately reflects the behavior that Derived’s "empty" constructor must offer. No matter how sophisticated a compiler’s exception implementation, Derived’s constructor must at least call constructors for its data members and base class, and those calls (which might themselves be inlined) could affect its attractiveness for inlining.

Library designers must evaluate the impact of declaring functions inline, because it’s impossible to provide binary upgrades to the client-visible inline functions in a library. In other words, if f is an inline function in a library, clients of the library compile the body of f into their applications. If a library implementer later decides to change f, all clients who’ve used f must recompile. This is often undesirable. On the other hand, if f is a non-inline function, a modification to f requires only that clients relink. This is a substantially less onerous burden than recompiling and, if the library containing the function is dynamically linked, one that may be absorbed in a way that’s completely transparent to clients.

Most debuggers have trouble with inline functions. This should be no great revelation. How do you set a breakpoint in a function that isn’t there? Although some build environments manage to support debugging of inlined functions, many environments simply disable inlining for debug builds.

Things to Remember:

  • Limit most inlining to small, frequently called functions. This facilitates debugging and binary upgradability, minimizes potential code bloat, and maximizes the chances of greater program speed.
  • Don't declare function templates inline just because they appear in header files.
原文地址:https://www.cnblogs.com/zoneofmine/p/15251204.html