前往
大廳
主題

C語言變數生命週期觀察與Lambda運算式,補充unique_ptr

Yang | 2024-02-08 13:30:32 | 巴幣 0 | 人氣 70

參考資料:

因為我目前的跨平台環境(Win10 x64/CentOS7/AIX7)似乎沒有都支援C14(make_unique),所以先測試C11的語法

好好使用unique_ptr可以避免孤兒記憶體(Memory Leak)和繁瑣的delete檢查,但又有些技巧可以轉移指標的所有權(move/shared_ptr),不過我覺得這種作法與"unique"的精神有些矛盾,先不測試和使用

#define STR(N) #N
#define XSTR(N) STR(N)

//__LINE__型態是%d,利用XSTR轉換成%s
#define _LINE_STR_ XSTR(__LINE__)

class MyClass
{
    public:
    int Value1;
    int Value2;

    public:
    MyClass()
    {
        Value1 = 10;
        Value2 = 20;

        printf("Constructor|%s|%s\n", _LINE_STR_, __FUNCTION__); //__FUNCTION__=="MyClass"
    }

    public:
    ~MyClass()
    {
        printf("Destructor|%s|%s\n", _LINE_STR_, __FUNCTION__); //__FUNCTION__=="~MyClass"
    }

    public:
    static MyClass *Factory()
    {
        return new MyClass();
    }
};

int main(const int argc, const char *argv[])
{
    { //unique_ptr練習0
        char *buf = new char[PATH_MAX]; //{0};
        int cnt = snprintf(buf, PATH_MAX, "%s", __FILE__);
        printf("%d|%ld|%ld|%s\n", cnt, sizeof(buf), strlen(buf), buf);
        delete[] buf;

        //unique_ptr不需要delete,離開作用域{}時,自動釋放資源
        std::unique_ptr<char[]> _ptr(new char[PATH_MAX]);
        cnt = snprintf(_ptr.get(), PATH_MAX, "%s", __FILE__);
        printf("%d|%ld|%ld|%s\n\n", cnt, sizeof(_ptr.get()), strlen(_ptr.get()), _ptr.get());
    }

    { //Lambda & unique_ptr練習1
        auto lambda = [](void) { return new MyClass(); };
        std::unique_ptr<MyClass> _uniPtr(lambda());

        //[&](void) { ... }(),建立匿名方法(Anonymous Method),並立刻執行
        //unique_ptr不能複製,因為匿名方法有傳遞unique_ptr,
        //所以只能用[&]傳址傳遞參數,用[=]傳值傳遞參數編譯會跳錯
        [&](void) { printf("unique_ptr test 1|%d|%s|%s\n", _uniPtr.get()->Value1, _LINE_STR_, __FUNCTION__); }();
    } //不需要delete,unique_ptr離開作用域{}時,自動會呼叫解構子~MyClass()

    { //Lambda & unique_ptr練習2
        std::function<MyClass *()> func = MyClass::Factory;
        std::unique_ptr<MyClass> _uniPtr(func());
        [&](void) { printf("unique_ptr test 2|%d|%s|%s\n", _uniPtr.get()->Value2, _LINE_STR_, __FUNCTION__); }();
    }

    { //Lambda & unique_ptr練習3
        try
        {
            std::unique_ptr<MyClass> _uniPtr1(new MyClass());
            [&](void) { printf("_uniPtr1|%d|%s|%s\n", _uniPtr.get()->Value1, _LINE_STR_, __FUNCTION__); }();

            std::unique_ptr<MyClass> _uniPtr2(new MyClass());
            [&](void) { printf("_uniPtr2|%d|%s|%s\n", _uniPtr.get()->Value2, _LINE_STR_, __FUNCTION__); }();

            throw std::invalid_argument("unique_ptr test 3");
        }
        //unique_ptr在作用域{}內發生exception,也仍然會觸發解構子~MyClass(),
        //所以資源仍然會被釋放,不會造成孤兒記憶體(Memory Leak)
        catch (const std::invalid_argument& e)
        {
            printf("Error test|std::invalid_argument|%s|%s|%s\n", e.what(), _LINE_STR_, __FUNCTION__);
        }
        //解構子和catch的設計要非常小心,內部如果又發生其他錯誤,程式還是會當掉,是很常見的大災難
    }

    return EXIT_SUCCESS;
}

觀察執行時間,利用unique_ptr管理資源,比自行設計new/delete,要多大約1 usec的時間,但可以簡化很多行程式碼

unique_ptr應該要放在生命週期較長的作用域{}內,再傳址(&)給其他生命週期較短的方法使用

以下範例紀錄一般指標_ptr的生命週期大於unique_ptr,程式沒當掉,但會得到錯誤的值,難以解釋,宜避免

MyClass *_ptr = NULL;

{
    _ptr = new MyClass();
    std::unique_ptr<MyClass> _uniPtr(_ptr);
} //unique_ptr離開作用域{},觸發解構子~MyClass()

//_ptr->Value1不是10,每次測試都是不同的亂數,不會引發錯誤,難以解釋
printf("unique_ptr test 4|%d|%s|%s\n", _ptr->Value1, _LINE_STR_, __FUNCTION__);
_ptr = NULL;
送禮物贊助創作者 !
0
留言

創作回應

更多創作