C++编程规范 8 标准库

本文概述了C++编程中的关键规范和STL库的最佳实践,包括避免使用auto_ptr、智能指针的选择、循环引用的解决、使用make_shared替代new生成shared_ptr、统一使用shared_ptr等。此外,还强调了不要保存string::c_str()指针、避免在不同模板库间混合使用数据类型、使用容器时评估内存碎片风险、string的高效替代以及推荐使用标准库容器的重要性。

C++编程规范 8 标准库

分类: C/C++ 编码规范   299人阅读  评论(0)  收藏  举报

8 标准库

STL标准模板库在不同产品使用程度不同,这里列出一些基本规则和建议,供各团队参考。

规则8.1 避免使用auto_ptr

说明:在stl库中的std::auto_ptr具有一个隐式的所有权转移行为,如下代码:

    auto_ptr<T> p1(new T); 
    auto_ptr<T> p2 = p1; 
当执行完第2行语句后,p1已经不再指向第1行中分配的对象,而是变为NULL。正因为如此,auto_ptr

不能被置于各种标准容器中。

转移所有权的行为通常不是期望的结果。对于必须转移所有权的场景,也不应该使用隐式转移的方式。
这往往需要程序员对使用auto_ptr的代码保持额外的谨慎,否则出现对空指针的访问。

使用auto_ptr常见的有两种场景,一是作为智能指针传递到产生auto_ptr的函数外部,二是使用

auto_ptr作为RAII管理类,在超出auto_ptr的生命周期时自动释放资源。

对于第1种场景,可以使用boost::shared_ptr或者是std::tr1::shared_ptr(在C++11标准中是

std::shared_ptr)来代替。

对于第2种场景,可以使用boost::scoped_ptr或者C++11标准中的std::unique_ptr来代替。其中

std::unique_ptr是std::auto_ptr的代替品,支持显式的所有权转移。

例外:

在C++11标准得到普遍使用之前,在一定需要对所有权进行转移的场景下,可以使用std::auto_ptr,

但是建议对std::auto_ptr进行封装,并禁用封装类的拷贝构造函数和赋值运算符,以使该封装类无法

用于标准容器。

规则8.2 仅将scoped_ptr、shared_ptr和unique_ptr用于管理单个对象

说明:boost::scoped_ptr、boost::shared_ptr、std::tr1::shared_ptr(在C++11标准中是

std::shared_ptr)和std::unique_ptr(C++11标准)都是用于管理单一对象的智能指针。当这些智能指

针在销毁所指向的对象时使用的都是delete而不是delete[],而使用delete删除数组是undefined行

为,因此不可使用上述智能指针管理数组。

当需要一个具有RAII特性的数组时,可以使用boost::scoped_array、boost::shared_array、

std::vector<std::tr1::shared_ptr>或std::vector<std::unique_ptr>(C++11标准)代替。

shared_ptr是基于引用计数的智能指针,可以安全用于大部分场景中,更新引用计数时需要略微消耗

一些性能,一般来说对性能不会有显著的影响。使用shared_ptr的另外一个需要注意的问题是不要产

生循环引用,基于引用计数的智能指针当出现循环引用时会造成内存泄漏。当需要循环引用时,其中

一个智能指针请使用weak_ptr。

boost::shared_ptr、std::tr1::shared_ptr(std::shared_ptr)的线程安全与stl中常见类型的线程一

致,即:

1. 多个线程可以同时读同一个shared_ptr对象;

2. 多个线程可以同时写不同的shared_ptr对象。

注:当多个不同的shared_ptr对象指向同一个底层对象时,同时写这些shared_ptr对象本身是线程安

全的,但是需要额外操作保证底层对象的线程安全。

规则8.3 如果涉及循环引用,使用weak_ptr解开循环

说明:当使用各种基于引用计数的shared_ptr时,会遇到循环引用的问题,例如:

    #include <memory> 
    class TChild; 
    class TParent 
    { 
    public: 
       void SetChild(std::shared_ptr<TChild> const& Child) 
       { 
          Child_ = Child; 
       }

    private: 
     std::shared_ptr<TChild> Child_; 
    };

    class TChild 
    { 
    public: 
       void SetParent(std::shared_ptr<TParent> const& Parent) 
        { 
           Parent_ = Parent; 
        }

    private: 
        std::shared_ptr<TParent> Parent_; 
    };

    int main() 
    { 
        std::shared_ptr<TParent> Parent = std::make_shared<TParent>(); 
        std::shared_ptr<TChild> Child = std::make_shared<TChild>();

       Parent->SetChild(Child); 
       Child->SetParent(Parent);

        //到这里Parent和Child产生了循环引用,当Parent、Child超出作用域后将产生内存泄漏。 
    } 
为了解决循环引用导致的内存泄漏,需要引入weak_ptr。将代码修改成下面这样:

    class TChild 
    { 
    public: 
       void SetParent(std::shared_ptr<TParent> const& Parent) 
        { 
           Parent_ = Parent; 
        }

       void UseParent() 
        { 
           //使用weak_ptr指向的对象之前通过lock()获得shared_ptr。 
           std::shared_ptr<TParent> Parent = Parent_.lock(); 
        }

    private: 
        std::weak_ptr<TParent> Parent_; 
    }; 
注意红色部分以及新增的UseParent()函数,演示了如何使用weak_ptr。另外需要注意的是SetParent()

函数中形参依然是shared_ptr,这样将使用了weak_ptr的细节隐藏了起来,从外部看全部是

shared_ptr。举例使用的是C++11标准中的std::shared_ptr,但是对于boost::shared_ptr和

std::tr1::shared_ptr同样适用。

规则8.4 使用make_shared代替new生成shared_ptr

说明:在代码中,我们可以使用形如std::shared_ptr<T> A(new T)的方式初始化shared_ptr。但是在

涉及到shared_ptr的地方使用new涉及到3个潜在的风险。

一是容易出现下面的代码,访问悬空指针:

    T* A = new T; 
    std::shared_ptr<T> B(A); 
    A->xxxxx; //当B超出作用域后:A指向的内存被释放,访问出错 
二是容易出现下面的代码,引起重复delete:

    T* A = new T; 
    std::shared_ptr<T> B(A);

    //在许多代码之后再次出现: 
    std::shared_ptr<T> C(A); 
当使用一个原生指针初始化一个shared_ptr时,引用计数会被置为1,于是出现了2组独立的引用计数 ,

当这2组引用计数到达0时都会引发销毁对象的操作,于是就会出现重复delete的问题。

三是可能出现内存泄漏的风险,考虑如下代码:

    int func1(); 
    void func2(std::shared_ptr<T> const& P1, int P2);

    int main() 
    { 
       func2(std::shared_ptr<T>(new T), func1()); 
    } 
在以上调用func2的代码中,根据编译器不同,可能会以以下顺序执行代码:

1. new T

2. func1()

3. std::shared_ptr<T>构造

在这种情况下如果在func1()中抛出了异常,将会造成new T泄漏。所以建议使用new初始化的

shared_ptr要放入单独的语句中,即将调用func2的代码修改为:

    std::shared_ptr<T> temp(new T); 
    func2(temp, func1()); 
使用以上方法相对略微烦琐,多引入了一个变量。跟shared_ptr配套存在的make_shared可以解决使用

new初始化的问题。上述两种情况下的代码可以修改为如下:

    std::shared_ptr<T> B = std::make_shared<T>(); 
    func2(std::make_shared<T>(), func1()); 
make_shared模板会构造一个指定类型的对象和一个shared_ptr并用该对象初始化shared_ptr,以上操

作是一步完成的,不存在中间抛出异常的风险。对于构造时需要参数的类型,将参数加在make_shared

模板后面的括号中即可。

延伸阅读材料: 《Effective C++中文版 第三版》 [美]Scott Meyers著 侯捷译 2006 电子工业出版

社 75页 条款17: 以独立语句将newed对象置入智能指针

规则8.5 对于同一个对象一旦使用shared_ptr,后续就要处处使用shared_ptr

说明:规则8.4描述了混用原生指针和shared_ptr容易导致问题:使用悬空指针和重复释放。所以,同

一对象的指针要统一用法,要么使用原生指针,要么使用shared_ptr,不要混用。

规则8.6 对于返回自身的shared_ptr指针的对象,要从enable_shared_from_this类派生

说明:对于需要使用shared_ptr管理的对象,当需要this指针时也需要使用对应的shared_ptr,但是

从3.4中可以看出,如果直接使用shared_ptr<T>(this)构造一个shared_ptr将会导致严重错误。为此,

boost和stl都提供了对应的enable_shared_from_this类,该类提供了一个shared_from_this()函数返

回this指针对应的shared_ptr。

示例:

    class TClass : public std::enable_shared_from_this<TClass> 
  { 
    public: 
       std::shared_ptr<TClass> GetSelf() 
       { 
          return shared_from_this(); 
       }

       std::shared_ptr<TClass const> GetSelf() const 
       { 
          return shared_from_this(); 
       } 
    };

规则8.7 不要将使用不同版本stl、boost等模板库编译的模块连接在一起

说明:模板库大量使用了内联函数,不同版本的模板库编译的模块对同一种数据类型的操作都已固化

在该模块中。如果不同版本的模板库中同一种数据类型的结构或者内存布局不同,在一个模块中定义

的对象被另外一个模块操作时可能会产生严重的错误。因为静态连接的模块常常不会划分出明确的接

口,常常会相互访问其它模块中定义的对象。

例外:

有些现存的模块已经无法获得源码,不可能重新使用同一版本的模板库重新编译。在这种情况下,如

果模块定义了清晰的接口,且接口中没有传递存在风险的数据类型,可以谨慎地混用,但是一定要进

行充足的验证。

规则8.8 不要保存string::c_str()指针

说明:在C++标准中并未规定string::c_str()指针持久有效,因此特定stl实现完全可以在调用

string::c_str()时返回一个临时存储区并很快释放。所以为了保证程序的移植性,一定不要保存

string::c_str()的结果,而是在每次需要时直接调用。

示例://不好的例子:

    std::string DemoStr = "demo"; 
    const char* buf = DemoStr.c_str(); 
    //在这里buf指向的位置有可能已经失效。 
    strncpy (info_buf,buf, INFOBUF_SIZE - 1);

建议8.1 不要将stl、boost等模板库中的数据类型传递到动态链接库或者其它进程中

说明:跨动态链接库或者其它进程还存在另外一个更复杂的问题,一般来说,内存分配与释放要在同

一个模块中,如果将容器或者其它数据类型传递到其它模块中由其它模块进行了内存分配释放操作,

将会造成不可预知的问题,通常是程序崩溃或者数据消失等但是也有可能在某些情况下程序完全正常

运行,因此定位错误会比较困难。

建议8.2 使用容器时要评估大量插入删除是否会生成大量内存碎片

说明:不同的操作系统和运行时库分配内存的策略各不相同,由于容器内存多是动态分配而来,对于

反复大量插入删除的操作,有可能会造成大量的内存碎片或者内存无法收回。

对于长期运行的服务程序,建议在使用容器时要对容器是否会造成内存碎片进行评估和测试,如果存

在风险的,可以使用内存池(如boost::pool)来避免这个问题。
建议8.3 使用string代替char*

说明:使用string代替char*有很多优势,比如:

1. 不用考虑结尾的’\0’;

2. 可以直接使用+, =, ==等运算符以及其它字符串操作函数;

3. 不需要考虑内存分配操作,避免了显式的new/delete,以及由此导致的错误;

需要注意的是某些stl实现中string是基于写时复制策略的,这会带来2个问题,一是某些版本的写时

复制策略没有实现线程安全,在多线程环境下会引起程序崩溃;二是当与动态链接库相互传递基于写

时复制策略的string时,由于引用计数在动态链接库被卸载时无法减少可能导致悬挂指针。因此,慎

重选择一个可靠的stl实现对于保证程序稳定是很重要的。

例外:

当调用系统或者其它第三方库的API时,针对已经定义好的接口,只能使用char*。但是在调用接口之

前都可以使用string,在调用接口时使用string::c_str()获得字符指针。

当在栈上分配字符数组当作缓冲区使用时,可以直接定义字符数组,不要使用string,也没有必要使

用类似vector<char>等容器。

建议8.4 使用stl、boost等知名模板库提供的容器,而不要自己实现容器

说明:stl、boost等知名模板库已经提供较完善的功能,与其自行设计并维护一个不成熟且不稳定的

库,不如掌握和使用标准库,标准库的使用经验在业界已有成熟的经验和使用技巧。

建议8.5 使用新的标准库头文件

说明:使用stl的时候, 头文件采用<vector>、<cstring>等,而不是<vector.h>、<string.h>。 

1. What is an IDE (Integrated Development Environment), and what are its main components? 2. What is the role of a compiler in the C++ development process? 3. What is the difference between source code (e.g., a .cpp file) and an executable file? 4. In the "Hello, World!" program, what is the purpose of the line #include <iostream>? 5. What is special about the main() function in a C++ program? 6. Why do computers fundamentally operate using the binary (base-2) system? 7. What is the base of the hexadecimal system? Why is it often used by programmers as a shorthand for binary numbers? 8. Explain the "triad" method for converting an octal number to binary. 9. Briefly describe the "division by 2" method for converting a decimal number to binary. 10. What is the decimal value of the binary number 1011? 1. What is the purpose of the std::cout object? Which header file must be included to use it? 2.What is the difference between an escape sequence like \n and a manipulator like std::endl? (Hint: Both create a new line, but they have a subtle difference). 3.How would you print the following text to the console, including the quotes and the backslash: He said: "The file is in C:\Users\"? 4.Is it possible to write an entire multi-line text output using only one std::cout statement? If yes, how? 5.What is a syntax error? Give an example of a syntax error from Task 2. (Task 2: Debugging The following program contains several syntax errors. Copy the code into your IDE, identify the errors, fix them, and run the program to ensure it works correctly. Incorrect Code: */ Now you should not forget your glasses // #include <stream> int main { cout << "If this text" , cout >> " appears on your display, cout << " endl;" cout << 'you can pat yourself on ' << " the back!" << endl. return 0; "; ) Hint: Pay close attention to comments, header files, brackets ({}), operators (<<), semicolons, and how strings and manipulators are written.) 1. What is the difference between variable declaration and initialization? 2.What will be the result of the expression 7 / 2 in C++? Why? 3.What will be the result of the expression 10 % 3? What is the main purpose of the modulus operator? 4. What is the purpose of std::cin and the >> operator? 5. A beginner tries to swap two integer variables a and b with the code a = b; b = a;. Why will this not work correctly? 1. What is an algorithm? Name the primary ways to represent an algorithm. 2.List the main flowchart symbols and explain their purpose. 3.What are the three fundamental types of algorithm structures? Briefly describe each. 4.In a branching algorithm, what determines the flow of execution? 5.What is the key characteristic of a linear algorithm? 6.When is a cyclic algorithm structure used?7. 8. 9. 7.Explain the purpose of a connector in a flowchart. 8.What is the difference between a predefined process block and a standard process block? 9.In the context of solving a quadratic equation algorithm, what condition must be checked before calculating the roots? Why? 1. What are the three main approaches to data input and output offered by C++? 2. What is the purpose of the SetConsoleOutputCP(65001) and SetConsoleCP(65001)
functions in the provided C++ program example? 3. Explain the difference between the cin and cout objects in Stream 1/0. 4. When using formatted 1/0, which header file must be included to use manipulators like setw and setprecision? 5. List three manipulators used for data output in C++ and briefly describe what each one does. 6. In Formatted I/0 using printf), what are the conversion specifications for a decimal integer and a real number in exponential form? 7. What is the difference in how the & (address-of) operator is used when inputting a value for an integer variable versus a string variable using the scanf() function? 8. Which Character I/O function is used to output a single character to the screen, and which is used to output a string? 9. Describe the syntax and function of the ternary operator in C++. 10. What is the difference between the logical AND (&&) and logical OR (I|) operators when combining multiple conditions? 11. When is the default label executed in a C++ switch statement? 12. What is the primary purpose of the break statement within a switch block? 1. What is the main purpose of using loops in programming? 2. Explain the key difference between the for, while, and do while loops. 3. What happens if you forget to include the increment/decrement statement in a while loop? 4. How can you interrupt an infinite loop during program execution? 5. What is the role of the setw() and setfill) manipulators in C++? 6. In a nested loop, how does the inner loop behave relative to the outer loop? 7. What is type casting, and why is it used in loop calculations? 8. How does the do while loop differ from the while loop in terms of condition checking? 9. What output formatting options can be used to align numerical results in columns? 10*. How would you modify a loop to skip certain iterations based on a condition? 1. List the six main biwise operators in C++ and explain the function of each. 2. Why cannot bitwise operations be applied to variables of floating-point type? 3. Explain the purpose of the << (left shift) and >> (right shift) operators. What is the typical effect on the decimal value of a number when it is shifted left by 1? Shifted right by 1? 4. Describe the process of using a mask to check the value of a specific bit within an
integer. 5. How can you use the bitwise AND operator (&) to check if a number is even or odd?
Explain the logic. 6. What is the difference between the logical AND (&&) and the bitwise AND (&)? Provide an example scenario for each. 7. Explain the purpose of the ~ (bitwise NOT) operator. What is the result of applying it to a mask, and how can this be useful? 1. What is the primary goal of program debugging? What types of errors can it help identify? 2. Describe the difference between Step Over (F10) and Step Into (F11) debugging commands. When would you choose one over the other? 3. What is the purpose of a breakpoint in planned debugging? How do you set and remove a breakpoint in Visual Studio? 4. Explain the utility of the "Watch" window compared to the "Autos" or "Locals" windows during a debugging session. 5. What is the key difference between the Debug and Release configurations when building a project? Why is it necessary to create a Release version after successful debugging? 6. List at least three types of files commonly found in a project's Debug folder and briefly state their purpose (e.g., *.pdb). 7. During debugging, you notice a variable has an incorrect value. How can you change its value during runtime to test a hypothesis without modifying the source code? 8. What command is used to exit the debug mode and stop the current debugging session? 1. What is an array in C++? List its three main characteristics. 2. How are array elements numbered in C++? What is the valid index range for an array declared as int data[25];? 3. Explain the difference between array declaration and initialization. Provide an example of each. 4. What is an initializer list? What happens if the initializer list is shorter than the array size? 5. How can you let the compiler automatically determine the size of an array during initialization? 6. What values do elements of a local array contain if it is declared but not explicitly initialized? How does this differ from a global array? 7. What is an array out-of-bounds error? Why is it dangerous, and what are its potential consequences? 8. How do you calculate the number of elements in an array using the sizeof operator?
Provide the formula. What is a significant limitation of this method? 9. Why is it impossible to copy the contents of one array into another using the assignment
operator (arrayB = arrayA;)? What is the correct way to perform this operation? 10. Why does comparing two arrays using the equality operator (arrayA == arrayB) not check if their elements are equal? How should array comparison be done correctly? 11. What does the name of an array represent in terms of memory? 1. What is a pointer in C++ and what are its two main attributes? 2. Explain the difference between the & and * operators when working with pointers. 3. Why is pointer initialization critical and what dangers do uninitialized pointers pose? 4. What is the fundamental relationship between arrays and pointers in C++? 5. How does pointer arithmetic work and why does ptr + 1 advance by the size of the pointed type rather than 1 byte? 6. What is the difference between an array name and a pointer variable? Why can't you increment an array name? 7. What are the differences between const int*, int* const, and const int* const? 8. How can you safely iterate through an array using pointers, and what are the boundary risks? 9. What is a null pointer and why should you check for nullptr before dereferencing? 10. How do you access array elements using pointer syntax, and how does the compiler translate arr[i] internally? 1. What is a multidimensional array? How is a two-dimensional array structured in memory? 2. Explain the concept of an "array of arrays". How does this relate to the declaration int arr/ROWS//COLS;? 3. The name of a two-dimensional array without indices is a pointer constant. What does this pointer point to? What do the expressions *(A + i) and *(*(A + i) +j) mean for a two-dimensional array A? 4. Describe the different ways to access the element A/1/[2/ of a two-dimensional array
using pointers. 5. What is the rule for omitting the size of dimensions when initializing and when passing a multidimensional array to a function? Why is it allowed to omit only the first dimension? 6. Explain the principle of "row-major order" for storing two-dimensional arrays in memory.
How does this affect element access? 7. Why are nested loops the standard tool for processing multidimensional arrays?
Describe the typical pattern for iterating through a matrix. 1. How is a character string stored in memory in C++? What is the role of the null terminator (10), and why is it critical for C-style strings? 2. Why must the size of a char array declared to hold a string be at least one greater than the number of characters you intend to store? 3. The array name without an index is a pointer constant. What does the name of a char array point to? 4. What are the two main ways to initialize a C-style string? What is a common mistake when using the initializer list method, and what is its consequence? 5. Why is it necessary to add _CRT_SECURE_NO_WARNINGS to the preprocessor definitions in Visual Studio when working with many standard C library functions?
What is the alternative approach? 6. What is the key difference between stropy and strncpy? Why might strncpy be considered safer? 7. How does the stremp function determine if one string is "less than" another? Why can't you use the == operator to compare two C-style strings for content equality? 8. Describe the purpose and parameters of the strok function. How do you get all tokens from a string? 9. What do the functions strchr and strrchr do? How do they differ? 10. Explain what the strstr function returns and what it is commonly used for. 11. What is the purpose of the functions in the < cctype> header? Give three examples of such functions and their use. 12. What is the difference between tolower(c) and_tolower(c)? When should you use each? 1. What is a function in C++? Name the three core benefits of using functions in a program. 2. What is the difference between a function declaration (prototype) and a function definition? Provide examples. 3. What is a function signature? Which elements are part of the signature, and which are not? 4. What methods of passing parameters to a function do you know? Explain the difference between pass-by-value, pass-by-pointer, and pass-by-reference. 5. Why can't you pass an array to a function by value? What is the correct way to pass an array to a function? 6. What is variable scope? How is it related to functions? 7. How does a function return a value? What happens if a function with a non-void return type does not return a value on all control paths? 8. Can you use multiple return statements in a single function? Provide an example. 9. What is function overloading? What is it based on? 10. How is interaction between functions organized in a program? Provide an example program with several functions. 11. What are default parameters? How are they specified, and in what cases are they useful? 12. How can you prevent a function from modifying the data passed to it? What modifiers are used for this? 13. What is recursion? Provide an example of a recursive function. 14. What common errors occur when working with functions? How can they be avoided? 15. How do you use pointers to functions? Provide an example of declaring and calling a function through a pointer. 用中文回答
最新发布
11-18
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值