C++ 11
书籍:
日常查阅资料看到的有用文章链接:
对于智能指针的使用,实际上是对所有权和生命周期的思考,一旦想明白了这两点,那对智能指针的使用也就得心应手了。
https://suzulang.com/try-libzip-2-compress/
https://stackoverflow.com/questions/45131984/customize-stdshared-ptr-deleter
篇一: 用 std::vector 包装 std::string 字符串数组,并将它们转为 const char ** 输出
分别使用了非 vector 方法,vector 方法和带有共享指针的方法,都可以正常使用,不过在使用 std::string.c_str() 给 char * 赋值的时候,并不会创建新的内存,只是返回 std::string 内部的指针,也就是说,std::string 占用的是栈内存,并不需要显式释放
char** arr = new char*[arrayObj]; if (arrayObj) { for (size_t i = 0; i < arrayObj; i++) { uint32_t size = parameters["url"][i].asString().size() + 1; arr[i] = new char[size]; strcpy_s(arr[i], size, parameters["url"][i].asString().c_str()); } }
void old_func(char** carray, size_t size) { for(size_t i = 0; i < size; ++i) std::cout << carray[i] << '\n'; } int main() { std::vector<std::string> strings {"one", "two", "three"}; std::vector<char*> cstrings; cstrings.reserve(strings.size()); for(size_t i = 0; i < strings.size(); ++i) cstrings.push_back(const_cast<char*>(strings[i].c_str())); // Do not change any of the strings here as that will // invalidate the new data structure that relies on // the returned values from `c_str()` // // This is not an issue after C++11 as long as you don't // increase the length of a string (as that may cause reallocation) if(!cstrings.empty()) old_func(&cstrings[0], cstrings.size()); }
std::vector<std::shared_ptr<std::string>> url_; for (size_t i = 0; i < arrayObj; i++) { url_.push_back( std::make_shared<std::string>(parameters["url"][i] .asString())); } std::vector<const char*> url_list; url_list.reserve(url_.size()); for (size_t i = 0; i < url_.size(); ++i) url_list.push_back(const_cast<char*>(url_[i].get()->c_str())); url_list.push_back(NULL); std::cout << url_list[0] << '\n'; std::cout << url_list[1] << '\n';
共享指针出了作用域会计数减一,当出现 url_(url) 这样的操作,计数也会加一
一些关于 c_str() 的解释:
std::string 内部的元素,是通过 c_str() 来的,c_str() 并不会再分配内存,仅仅是返回 std::string 的一个内部引用的指针,所以 std::string 出了作用域被释放了, 自然 char* 指向的内存就已经被释放了 ,因为是同一块内存 。
std::string 本质上就是用一个 class 封装了一个 数组 ,c_str() 就是返回的内部数组的首地址。
但是在用 c_str() 返回的指针的时候, 要保证 string 本身不被修改,比如一个 string 内部数组 是 char * internal = "12345"; , c_str() 返回的就是internal 的首地址,首地址被修改,可能是 append了,那么首地址还是有效的,只要 `\0` 存在,那么只是内容变了; 也可能是原来的数组不够长,内部重新分配了内存,那么 首地址就变了,这时候 之前 c_str() 获取到的地址 就无效了。那么去访问这个 无效的 c_str(),就会引发内存异常。 但是内部 重新分配,一定是 先分配内存,再copy内容,再释放之前的内存; 所以内存管理是做好了的。
链接:https://www.cplusplus.com/reference/string/string/c_str/
文档指出:The pointer returned may be invalidated by further calls to other member functions that modify the object.
一个小例子:
std::vector<const char*> url_;
for (size_t i = 0; i < 2; i++) { std::string sss = parameters["url"][i].asString(); url_.push_back(const_cast<char*>(sss.c_str())); }
for 循环两次后会发现 url_ 的第一条字符串变成乱码,也就是说 sss.c_str() 首地址由于第二条字符串的加入而变为无效地址。
如果是挨个赋值就不会有这样的问题,
url_.push_back(const_cast<char*>(sss.c_str())); url_.push_back(const_cast<char*>(www.c_str())); url_.push_back('\0');
// std::string 转为 const char** 后,传给接口 char** url_list = new char*[url_.size() + 1]; char** temp_ptr = url_list; for (size_t i = 0; i < url_.size(); i++) { *temp_ptr = new char[url_[i].size() + 1]; strcpy_s(*temp_ptr++, url_[i].size() + 1, url_[i].c_str()); } // bvclive_live_set_url_list 是 C 接口,通过 while 遍历数组 // 故需要额外补充 nullptr *temp_ptr = nullptr;
或者
const char** url_list = new const char*[url_.size() + 1]; const char** temp_ptr = url_list; for (size_t i = 0; i < url_.size(); i++) { *temp_ptr++ = url_[i].c_str(); }
智能指针借助 lambda 自动释放
std::shared_ptr<int> p7(new int[10], [](int* p) {delete[]p; });
常用智能指针的使用:
bvclive_engine_ = BvcLiveEngine::New(chained_push_url_, audio_format_); std::shared_ptr<BvcLiveEngine> BvcLiveEngine::New(std::vector<std::string> url, AudioFormat format) { auto engine = new BvcLiveEngine(url, format); std::shared_ptr<BvcLiveEngine> res(engine); if (!res->StartBvclive()) { return nullptr; } return res; }
静态函数的执行顺序:
- 静态初始化块的优先级最高,也就是最先执行,并且仅在类第一次被加载时执行;
- 非静态初始化块和构造函数后执行,并且在每次生成对象时执行一次;
- 非静态初始化块的代码会在类构造函数之前执行。因此若要使用,应当养成把初始化块写在构造函数之前的习惯,便于调试;
- 静态初始化块既可以用于初始化静态成员变量,也可以执行初始化代码;
- 非静态初始化块可以针对多个重载构造函数进行代码复用。
一文看懂静态初始化块、静态成员、初始化块、构造函数执行顺序以及用途
拷贝构造函数的深浅拷贝:C++拷贝构造函数(深拷贝,浅拷贝)
c++ 父类指针如何操作子类的新函数:
Base* b = new Derived(); b->Func(); // 不安全的转换 Derived* d = (Derived*)b; d->NewFunc(); --------------------------------------- Base* b = new Derived(); b->Func(); // 安全转换 Derived* d = dynamic_cast <Derived*>(b); if (d != NULL) { d->NewFunc(); }
如果要将子类指针指向父类对象,则需要做强制类型转换
之所以使用父类指针指向子类对象(A *p = new B;),目的就是实现多态,通过父类指针可以指向多个不同的子类对象,向不同的对象发送同一个消息,不同的对象在接受时会产生不同的行为。但是根据内存的解释,父类指针无法访问子类的独有的函数,因此这个用法主要是用来实现多态(执行期的动态绑定)
一:虚函数表指针(vptr)创建时机
vptr跟着对象走,所以对象什么时候创建出来,vptr就什么时候创建出来,也就是运行的时候。
当程序在编译期间,编译器会为构造函数中增加为vptr赋值的代码(这是编译器的行为),当程序在运行时,遇到创建对象的代码,执行对象的构造函数,那么这个构造函数里有为这个对象的vptr赋值的语句。
二:虚函数表创建时机
虚函数表创建时机是在编译期间。编译期间编译器就为每个类确定好了对应的虚函数表里的内容。
所以在程序运行时,编译器会把虚函数表的首地址赋值给虚函数表指针,所以,这个虚函数表指针就有值了。
这也是为什么构造函数不能为虚函数。因为在创建对象时会调用构造函数,构造函数在这时初始化对象的虚表指针。如果构造函数是虚函数,那么意味着对象必须要通过虚表指针去调用构造函数,但是在调用构造函数之前,虚表指针还没被赋值,这就出现了矛盾。
另读:C++类内存分布
在 const 成员函数中,既不能改变 this 所指向的对象,也不能改变this所保存的地址,this 的类型是一个指向 const 类型对象的 const 指针。
当 const 在函数名前面的时候修饰的是函数返回值。
当 const 在函数名后面表示是常成员函数,该函数不能修改对象内的任何成员,只能发生读操作,不能发生写操作。
inline 用法:
(1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。
(2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。类的构造函数和析构函数容易让人误解成使用内联更有效。要当心构造函数和析构函数可能会隐藏一些行为,如"偷偷地"执行了基类或成员对象的构造函数和析构函数。所以不要随便地将构造函数和析构函数的定义体放在类声明中。一个好的编译器将会根据函数的定义体,自动地取消不值得的内联(这进一步说明了 inline 不应该出现在函数的声明中)。
最令人烦恼的还是当编译器拒绝内联的时候。在老的实现中,结果很不尽人意,虽然在新的实现中有很大的改善,但是仍然还是不那么完善的。一些编译器能够足够的聪明来指出哪些函数可以内联哪些不能,但是大多数编译器就不那么聪明了,因此这就需要我们的经验来判断。如果内联函数不能增强性能,就避免使用它!
#include<stdio.h> #ifdef __cplusplus extern "C"{ #endif void testCfun(); #ifdef __cplusplus } #endif