【C++】跨平台开发注意事项【下】

简介: 在 Windows 平台上适用的 C++ 代码移植到 Linux 下的注意事项

IV - 编译器语法检查

4.6 - 宏函数参数

宏函数使用时,参数个数需要与定义时保持一致

MSVC 有较高容错,宏函数的参数个数错误,即参数数量与定义不一致时,或不影响使用,但 Linux 下无法编译通过。

#define OUTPUT(a,b)\
 std::cout << b << a << std::endl;

// 使用处
OUTPUT("test");

Linux 下编译报错为 宏 "OUTPUT" 需要 2 个参数,但只给出了 1 个。

4.7 - 局部变量引用

不使用局部变量初始化引用

Linux 下 gcc/g++ 编译器报错为无法绑定非常量左值引用至右值。
错误示例

// method
std::vector<std::string> Database::InquireClientNames(const std::string & type)
{
   
    std::vector<std::string> vret;
    // ...

    return vret;
}

// call
auto & vec = Database::GetInstance()->InquireClientNames("Remote");

编译错误原文为

cannot bind non-const lvalue reference of type 'std::vector<std::__cxx11::basic_string<char>>&' to an rvalue of type 'std::vector<std::__cxx11::basic_string<char>>'

使用函数返回一个局部变量去初始化引用,无法绑定非常量左值引用至非引用同类型右值。

被调用函数返回时,首先拷贝局部变量 vret 至一个栈区的临时变量,在调用处使用脸是变量初始化引用 vec ,由于标准 C++ 中,临时变量或对象的生命周期在一个完整的语句表达式结束后便宣告结束,也就是在语句
auto & vec = Database::GetInstance()->InquireClientNames("Remote"); 之后生命周期宣告结束,所以 vec 面临无效的危险,很有可能之后使用的 vec 是一个无法确定的值。

如果真的需要使用引用接收返回值,则需要函数内部返回一个全局变量,静态变量或对象内部的变量,所有使用时生命周期未结束的内存,即非 automatic duration 的内存, 且函数声明返回值类型为引用。

automatic duration 见附录 6.1 - Storage Duration

4.8 - 布尔值与指针

不使用布尔类型 (false) 到空指针 (nullptr) 的隐式转换

除开在章节 4.5 中描述的,在 Visual Studio 的某些 VC 版本中,false 可以与 nullptr 等价使用。 Linux 下编译报错为

cannot convert 'bool' to 'DataStruct *' in return

无法在返回时转换布尔类型为 DataStruct 类型的指针。

错误举例

// 示例 1 
virtual DataStruct * ClientNames() {
    return false; }

// 示例 2
DataStruct * ClientAdress(DataStruct * Dp)
{
   
    CHECK_NULLPTR(Dp);
}

// CHECK_NULLPTR 定义为
#define CHECK_NULLPTR(ptr)\
{\
    if (nullptr == ptr)\
    {\
        return false;\
    }\
}

示例 2 同样为宏误用,CHECK_NULLPTR 返回 false ,在 Linux 下无法与指针发生隐式转换。

需更改为

// 示例 1 
virtual DataStruct * ClientNames() {
    return nullptr; }

// 示例 2
DataStruct * ClientAdress(DataStruct * Dp)
{
   
    // 手动判断空指针,或自行实现一个返回 nullptr 的宏。
    if (!Dp)
    {
   
        return nullptr;
    }
}

V - 其他问题

5.1 - 可变长参数

Linux 下宏函数使用 ... 与宏 __VA_ARGS__ 会编译不通过。

#define VA_CALLBACK(identifierstring, ...)\
{\
    CustomizeCallBack::GetInstance()->Call(identifierstring, __VA_ARGS__);\
}

Linux 下编译报错为

expected primary-expression before '{' token.

个人的解决方案为使用可变长模板函数,解决方案不唯一

template<typename... Args>
bool VA_CALLBACK(const std::string & iden, Args... args)
{
   
    return CustomizeCallBack::GetInstance()->Call(iden, std::forward<Args>(args)...);
}

5.2 - 类型与对象歧义

std::is_convertible 在 Win 与 Linux 平台如下代码编译时会产生歧义。

template <typename _Func, typename... Args>
int Register(Type type, const std::string & iden, _Func && _func, Args&&  ... args)
{
   
    return _Register(std::is_convertible<_Func, Func>::type(), type, iden, std::forward<_Func>(_func), std::forward<Args>(args)...);
}

解决方法为:在 std::is_convertible 处添加 typename

template <typename _Func, typename... Args>
int Register(Type type, const std::string & iden, _Func && _func, Args&&  ... args)
{
   
    return _Register(typename std::is_convertible<_Func, Func>::type(), type, iden, std::forward<_Func>(_func), std::forward<Args>(args)...);
}

5.3 - 动态库

5.3.1 - 类方法导出标识

Windows 平台下动态库导出,一般使用 __declspec(dllexport) 标识,如下示例

class __declspec(dllexport) CustomizeCallBack
{
   
public:
//...
};

此标识在 Linux 下不可用,需要添加宏隔离,更改示例如下

#if defined(_WIN32) || defined(_MSC_VER) || defined(_WIN64)
class __declspec(dllexport) CustomizeCallBack
#elif defined(__linux__) || defined(__GNUC__)
class __attribute__((visibility("default"))) CustomizeCallBack 
#endif
{
   
//...
};

或者将跨平台的宏预先定义在一个头文件中,以简化代码

// common/macros.h
#if defined(_WIN32) || defined(_MSC_VER) || defined(_WIN64)
#define CUSTOMIZED_EXPORT __declspec(dllexport) 
#elif defined(__linux__) || defined(__GNUC__)
#define CUSTOMIZED_EXPORT __attribute__((visibility("default"))) 
#else 
#define CUSTOMIZED_EXPORT
#endif

// customizedCallBack.h
#include "common/macros.h"
class CUSTOMIZED_EXPORT CustomizeCallBack
{
   
public:
//...
};

5.3.2 - 静态库依赖特殊处理

Windows 平台下,编译静态库后,在使用此静态库依赖生成动态库时,不需要做特殊处理,但是相同的情境,Linux 平台下编译静态库时需要添加额外的编译选项 -fPIC ,需要在 CMakeList.txt 中添加分支特殊处理,示例:

if (UNIX)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
endif ()

5.4 - 操作符 typeid

通过调用操作符 typeid (typeid(变量).name()) 可获取此变量或对象运行时类型的字符串,但此操作在不同平台显示的内容不一致。Windows 平台 MSVC 编译器通常情况下可获取代码与变量类型代码一致的字符串,但 Linux 下字符串会差别很大,甚至对开发者而言不可读。

示例

class CustomizedCB1 : //...
{
   
public:
    CustomizedCB1(/*...*/)
    {
   
        m_name = typeid(this).name();
        //...
    }
protected:
    std::string m_name;
    //...
}

以上操作,通过 this 指针,Windows 平台上获取的字符串为 "CustomizedCB1" , 注意不是 “CustomizedCB1*”。而在 Linux 下使用相同的字符串去匹配时,会匹配失败。

解决方法如下:

#if  defined(_WIN32) || defined(_MSC_VER) || defined(_WIN64)
m_name = typeid(this).name();
#elif defined(__linux__) || defined(__GNUC__)
int demanglestatus(0);
m_name = abi::__cxa_demangle(typeid(*this).name(), 0, 0, &demanglestatus);
#else
m_name = "undefined";
#endif

注意:Linux 下需使用 this 指针解引用,替代 this,否则得到的字符串会带*

VI - 附录

6.1 - Storage Duration

All objects in a program have one of the following storage durations:

  • automatic storage duration. The storage for the object is allocated at the beginning of the enclosing code block and deallocated at the end. All local objects have this storage duration, except those declared static, extern or thread_local
  • static storage duration. The storage for the objects is allocated when the program begins and deallocated when the program ends. Only one instance of the object exists. All objects decalred at namespace scope (including global namespace) have this storage duration, plus those declared with static or extern.
  • thread storage duration. The storage for the objects is allocated when the thread begins and deallocated when the thread ends. Each thread has its own instance of the object. Only objects declared thread_local have this storage duration. thread_local can appear together with static or extern to adjust linkage.
  • dynamic storage duration. The storage for the objects is allocated and deallocated per request by using dynamic memory allocation functions.

存储期限
一个程序中的所有对象都有以下存储期限之一:

  • 自动存储期限。对象的存储是在封闭的代码块开始时分配的,在结束时删除释放。所有本地对象都有这个存储期限,除了那些声明为 staticexternthread_local 的对象。
  • 静态存储期限。对象的存储在程序开始时被分配,在程序结束时被删除释放。该对象只有一个实例存在。所有在命名空间范围(包括全局命名空间)的对象都有这个存储期限,加上那些用staticextern声明的对象。
  • 线程存储期限。对象的存储在线程开始时被分配,在线程结束时被删除释放。每个线程都有自己的对象实例。只有声明为thread_local的对象才有这个存储期限。thread_local可以和staticextern一起出现,以调整代码间的联系。
  • 动态存储期限。该类型存储期限对象的存储是通过使用动态内存分配函数按请求分配和删除释放的。
目录
相关文章
|
7月前
|
编译器 Linux C++
【C++ 跨平台开发 】掌握 C++ 跨平台关键宏的使用
【C++ 跨平台开发 】掌握 C++ 跨平台关键宏的使用
156 3
|
6月前
|
设计模式 算法 程序员
【C++】大气、正规的编程习惯:C++学习路径与注意事项
【C++】大气、正规的编程习惯:C++学习路径与注意事项
77 0
|
7月前
|
存储 算法 C语言
【编程陷阱】编写出色C++代码:遵循的注意事项和最佳实践
【编程陷阱】编写出色C++代码:遵循的注意事项和最佳实践
64 0
|
NoSQL Linux MongoDB
C++库封装mongodb(跨平台开发)
我的初衷是在Linux平台下只提供动态库和头文件,windows平台下提供静态库和头文件给开发者,这个库mongo-proxy对外提供了一些对mongodb的连接,增删改查,创建索引,聚合等操作的封装,开发者只需要关心接口如何调用,而不需要关心接口是如何实现的,也不需要关心mongo-c-driver的相关依赖,这里我抽象出mongo_proxy类,
|
C++
C和C++动态内存分配及内存注意事项(重要)
C和C++动态内存分配及内存注意事项(重要)
66 0
|
Java 程序员 Android开发
C++ 程序员,安卓开发注意事项
C++ 程序员,安卓开发注意事项
|
安全 Linux 编译器
【C++】跨平台开发注意事项【上】
将 Windows 平台上适用 C++ 代码移植到 Linux 下需要注意的事项
504 0
【C++】跨平台开发注意事项【上】
|
算法 安全 C语言
c++的lambda使用注意事项,可能导致的崩溃问题分析
c++的lambda使用注意事项,可能导致的崩溃问题分析
|
编译器 Linux API
C++中的可移植性和跨平台开发
在当今软件开发行业中,跨平台开发已经成为了一种非常流行的方式。C++作为一门强大的编程语言,也被广泛应用于跨平台开发中。然而,由于不同操作系统的差异和限制,C++在不同的平台上的表现可能会有所不同。为了解决这个问题,我们需要优化C++代码的可移植性,以便在不同的平台上实现相同的功能
183 0
|
存储 IDE 编译器
Android C++系列:函数返回值注意事项
函数返回值就是使用return语句终止正在执行的函数,看是很简单的问题有什么说的呢?因为越是简单的问题里面越是有一些不易发现的坑。
105 0