dimanche 3 juillet 2022

strange behavior for accessing the return value in a destructor in c++?

here is the code:

expected behavior: In ~X() the ptr shoule be != nil no matter the IS_ERR macro is defined or not

#include <iostream>
#include <functional>
#include <vector>
#include <memory>

struct X {
    X(std::function<void()> f): f_{std::move(f)} {}

    ~X() {
        f_();
    }

    std::function<void()> f_;
};

std::shared_ptr<int> func() {
    std::shared_ptr<int> ptr;

    X x([&] {
        std::cout << "dtor, ptr == null? " << (ptr == nullptr) << ", addr " << (long long)(&ptr) << std::endl;
    });

    if (ptr = std::make_shared<int>(100)) {
        std::cout << "return, ptr == null? " << (ptr == nullptr) << ", addr " << (long long)(&ptr) << std::endl;
        return ptr;
    }

#if defined(IS_ERR)
    return nullptr;
    // return, ptr == null? 0, addr 140732821766368
    // dtor, ptr == null? 1, addr 140732821766368 // ATTENTION: ptr == nil here !!!! the internal of ptr is moved to the caller before the destructor of X is executed even if there is no receiver in the caller. 
    // fini, ptr == null? 0, addr 140732821766576
#else
    // return, ptr == null? 0, addr 140732879315376
    // dtor, ptr == null? 0, addr 140732879315376 // ATTENTION ptr != nil  the expected behavior
    // fini, ptr == null? 0, addr 140732879315376
    return ptr;
#endif
}

int main()
{
    auto ptr = func();
    std::cout << "fini, ptr == null? " << (ptr == nullptr) << ", addr " << (long long)(&ptr) << std::endl;
}

  • in the defined(IS_ERR)

// ATTENTION: ptr == nil here !!!! the internal of ptr is moved to the caller before the destructor of X is executed even if there is no receiver in the caller.

  • in the no defined(IS_ERR)

// ATTENTION ptr != nil the expected behavior

  • the code is tested under followings enviornments:
g++ --version : g++ (GCC) 4.8.2 20140120 (Red Hat 4.8.2-15)
Apple LLVM version 10.0.1 (clang-1001.0.46.4) Target: x86_64-apple-darwin18.7.0

the test result is the same:

rm -f test && clang++ -o test test.cc -std=c++11  -Wno-parentheses && ./test | grep dtor    
dtor, ptr == null? 0, addr 140732827398560
rm -f test && clang++ -o test test.cc -std=c++11    -DIS_ERR  -Wno-parentheses && ./test | grep dtor   
dtor, ptr == null? 1, addr 140732832612560
rm -f test && clang++ -o test test.cc -std=c++11 -O3  -Wno-parentheses && ./test | grep dtor
dtor, ptr == null? 0, addr 140732887884168
rm -f test && clang++ -o test test.cc -std=c++11  -O3  -DIS_ERR  -Wno-parentheses && ./test | grep dtor
dtor, ptr == null? 1, addr 140732702822624
rm -f test && g++ -o test test.cc -std=c++11  -Wno-parentheses && ./test | grep dtor
dtor, ptr == null? 0, addr 140735881081728
rm -f test && g++ -o test test.cc -std=c++11    -DIS_ERR  -Wno-parentheses && ./test | grep dtor
dtor, ptr == null? 1, addr 140722084209920
rm -f test && g++ -o test test.cc -std=c++11 -O3  -Wno-parentheses && ./test | grep dtor
dtor, ptr == null? 0, addr 140736717254624
rm -f test && g++ -o test test.cc -std=c++11  -O3  -DIS_ERR  -Wno-parentheses && ./test | grep dtor
dtor, ptr == null? 1, addr 140735485800352
  • the use case of the example code is as follows:
using Error = std::shared_ptr<std::string>;
// go style error propagation is simulated here

Error func() {
  Error err;
 
    X x([&] {
        if (err) {
          // ATTENTION: we may do something here, like logging, alerting
        } else {
         // or we any do something REALLY REALLY IMPORTANT here, like express, transferring money ... it may cause catastrophic result ... (it is actually err != nil, but you think the result is ok ...) 
        }
    });
 err = doSomeThing()
if (err) {
  err = Wrap(err, "do func");
  return err;
}
#if defined(IS_ERR)
  return nullptr;
#else
  return err;
#endif
}  

Aucun commentaire:

Enregistrer un commentaire