遇到的细碎的问题及其解决方法。
函数参数传递 pass by value: 在栈区开辟了形参的内存空间,并调用拷贝构造函数 把实参复制给形参,如果资源较大,拷贝消耗较大。
pass by reference: 本质上是传递指针,通过指针间接寻址。
注意:当传递内置类型时,如int、char,指针占用的内存空间高于变量所占用的内存空间。而寻址会降低程序的效率,应当使用值传递并使用std::move
。传递STL容器时也可以使用值传递加移动语句。
函数返回值 在现代 C++ 中,返回临时对象实际上是可以的,因为编译器会进行返回值优化 (RVO - Return Value Optimization) 和移动语义优化。
安全的返回方式 :
std::string getName () { std::string name = "test" ; return name; } std::vector<int > getNumbers () { std::vector<int > nums = {1 , 2 , 3 }; return nums; }
不安全的返回方式 :
int * getValuePtr () { int value = 42 ; return &value; } int & getValueRef () { int value = 42 ; return value; }
关于对象返回的一般规则 :
返回值类型的对象是安全的(依赖 RVO 和移动语义)
不要返回局部变量的指针或引用
如果对象很大,考虑使用引用参数或智能指针
如果是自定义类,确保实现了移动构造函数和移动赋值运算符
性能考虑 :
std::vector<int > createVector () { return std::vector<int >{1 , 2 , 3 }; } void createLargeObject (LargeObject& out) { }
总结:
返回临时对象在现代 C++ 中是安全的
但要避免返回指向局部变量的指针或引用
对于大对象或特殊情况,可以考虑使用引用参数
确保你的类支持移动语义(如果需要的话)
如果没有移动构造函数,返回一个临时对象会再调用时创建一个新的对象(调用拷贝构造函数),如果没有手动实现的拷贝构造函数,浅拷贝可能出现内存泄漏。
不考虑代码设计问题,最安全的方式是传入需要修改的对象引用,直接对对象进行修改。
OpenGL手动控制帧率 通过计算渲染当前帧所消耗的时间,对比目标帧率时一帧应该消耗的时间,如果渲染当前帧所需时间更短,则让当前线程休眠目标帧率应消耗时间与当前帧渲染时间之差。
auto framerate = 120 ;auto time = Time::GetTime ();Timestep timestep = time - m_LastFrameTime; m_LastFrameTime = time; auto sleepDuration = std::chrono::duration_cast <std::chrono::milliseconds>(std::chrono::duration <float >(1.0 / framerate - timestep.GetSeconds ())); if (1.0 / framerate > timestep.GetSeconds ()) std::this_thread::sleep_for (sleepDuration);
但事实上这样会导致帧率的不稳定,目前还没找到更好的办法。
降低帧率最好的方法仍然是开启VSync。
warning C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失 一般是使用中文字符集编写的源代码,使用utf-8编码打开,会出现乱码,编译选项中添加/utf-8
即可。
如xmake.lua
:
参数包(parameter pack) 参数包是 C++11 中引入的一个特性,允许模板接受任意数量的参数。
参数包在模板中表示为 ...
,可以出现在模板参数列表、函数参数列表、函数参数类型列表、模板参数列表中,用来表示可变数量的参数。
1. 在变参类模板中使用 可以用任意数量的模板实参实例化:
template <class ... Types>struct Tuple {}; Tuple<> t0; Tuple<int > t1; Tuple<int , float > t2; Tuple<0 > error;
变参函数模板可以用任意数量的函数实参调用(模板实参通过模板实参推导 推导):
template <class ... Types>void f (Types... args) ; f (); f (1 ); f (2 , 1.0 );
类模板中,形参包必须是类模板形参列表中的最后一个。
template <typename U, typename ... Ts> struct valid ;
函数模板则可以出现在前面,只要其后所有的形参都可以从函数实参推导或默认拥有实参即可 。
template <typename ... Ts, typename U, typename =void >void valid (U, Ts...) ; valid (1.0 , 1 , 2 , 3 );
2.变参函数模板的函数形参列表中使用 可以在变参函数模板的函数形参列表中使用,表示多个类型的形参。
#include <iostream> template <typename ... Args>void printAll (Args... args) { ((std::cout << args << std::endl), ...); } int main () { printAll (1 , 2 , 3 , "hello" , 3.14 ); return 0 ; }
在这个示例中,printAll
函数接受任意数量的参数,使用折叠表达式展开参数包,并打印所有参数。
可以使用类型约束加包名的形式定义:
#include <iostream> template <std::intergal... Args>void printAll (Args... args) { ((std::cout << args << std::endl), ...); } int main () { printAll (1 , 2 , 3 , 4 , 5 ); printAll (1 , 2 , 3 , "hello" , 3.14 ); return 0 ; }
OpenGL世界坐标与窗口坐标 世界坐标就是物体在所创建的投影中的实际坐标,这些物体被投影矩阵转化成屏幕空间标准化坐标(NDC坐标),这些两个坐标(即世界坐标与NDC坐标)是通过mvp矩阵进行转化的。
而窗口坐标是显示在窗口中的像素的坐标,它与NDC可以通过归一化来转换。
因此,在OpenGL中,当鼠标点击窗口中的每一个坐标时,可以通过一系列转换,将此坐标转换为世界坐标,以此来映射一些动作。
auto & app = Application::Get ();float x = (2.0f * Input::GetMouseX ()) / app.GetWindow ().GetWidth () - 1.0f ;float y = 1.0f - (2.0f * Input::GetMouseY ()) / app.GetWindow ().GetHeight ();float z = 1.0f ;glm::vec4 screenPos = glm::vec4 (x, y, z, 1.0f ); glm::vec4 worldPos = glm::inverse (m_Camera->GetViewProjectionMatrix ()) * screenPos; worldPos /= worldPos.w; int chessX = static_cast <int >(std::round (worldPos.x));int chessY = static_cast <int >(std::round (worldPos.y));if (chessX >= -7 && chessX <= 7 && chessY >= -7 && chessY <= 7 ){ m_ChessBoard.Drop (chessX, chessY, ChessBoard::ChessColor::White); }
总体步骤为:
获取窗口坐标x,y
将窗口坐标转换为NDC坐标,此时坐标每个轴的值应在(-1,1)之间,z轴始终1.0f
获取投影矩阵和视角矩阵,如果有模型矩阵也要加上(即MVP矩阵)
获取转换矩阵的逆矩阵,与NDC坐标相乘,所得的坐标除以w轴以标准化
C++获取一个float值最近的整数 四舍五入:
float num = 3.6f ;int rounded = static_cast <int >(std::round (num));
向下舍入:
float num = 3.6f ;int floored = static_cast <int >(std::floor (num));
向上舍入:
float num = 3.6f ;int ceiled = static_cast <int >(std::ceil (num));
截取整数部分:
float num = 3.6f ;int truncated = static_cast <int >(num);
GLFW窗口图标设置 int width, height, channels;unsigned char * image = stbi_load ("./Gobang/assets/gobang.png" , &width, &height, &channels, 4 );if (image){ GLFWimage images[1 ]; images[0 ].width = width; images[0 ].height = height; images[0 ].pixels = image; glfwSetWindowIcon ((GLFWwindow*)window, 1 , images); stbi_image_free (image); } else { std::cerr << "Failed to load icon image" << std::endl; }
多线程通信 使用条件变量可以实现进程间的通信,以打断耗时循环。
std::mutex mtx; std::condition_variable cv; bool g_stop = false ;void childThreadHandle () { std::unique_lock<std::mutex> lock (mtx) ; g_stop = false ; while (!g_stop){ auto timeout = std::chrono::seconds (10 ); cv.wait_for (lock, timeout, [] { return g_stop; }); } } void stop () { std::lock_guard<std::mutex> lock (mtx) ; g_stop = true ; cv.notify_all (); }