返回值优化和析构函数调用

Return value optimization and destructor calls

本文关键字:函数调用 析构 优化 返回值      更新时间:2023-10-16

我知道RVO主要是应用的,但我能指望它吗?我有一个函数可以创建FlagContainer类的对象。

class FlagContainer {
public:
    ~FlagContainer() {
        someItem->flag = true;
    }
private:
    Item * someItem;
}
public FlagContainer createFlagContainer() {
    return FlagContainer();
}

调用方使用容器后,必须设置标志。所以我可以用析构函数来做这个。

{
    FlagContainer container = createFlagContainer();
    // do something with container
}

当超出范围时,将调用析构函数。但是我能确定在createFlagContainer中永远不会调用析构函数吗?有什么办法做到这一点吗?

我会使用AVR GCC 4.7.0编译器。

我知道RVO主要是应用的,但我能指望它吗?

不要依赖RVO的逻辑。简单地说,编译程序的人可以通过命令行选项将其关闭。

有什么办法做到这一点吗?

令人惊讶的是,标准库已经为您提供了这一功能,因此您不需要冒自己实现它的风险(众所周知,移动构造函数和运算符很难正确)

带有自定义deleter的std::unique_ptr可以很好地完成这项工作。

#include <iostream>
#include <memory>
#include <cassert>
// test type
struct X
{
    bool flag = false;
};

// a custom deleter that sets a flag on the target
struct flag_setter_impl
{
    template<class X>
    void operator()(X* px) const {
        if (px) {
            assert(!px->flag);
            std::cout << "setting flag!" << std::endl;
            px->flag = true;
        }
    }
};
// a type of unique_ptr which does not delete, but sets a flag
template<class X>
using flag_setter = std::unique_ptr<X, flag_setter_impl>;
// make a flag_stter for x
template<class X>
auto make_flag_setter(X& x) -> flag_setter<X>
{
    return flag_setter<X>(&x, flag_setter_impl());
}

// quick test
auto main() -> int
{
    using namespace std;
    X x;
    {
        auto fs1 = make_flag_setter(x);
        auto fs2 = move(fs1);
    }
    return 0;
}

但我的目标上没有STL

然后不要忘记你的规则0,3,5

#include <iostream>
#include <memory>
#include <cassert>
// test type
struct X
{
    bool flag = false;
};

// a custom deleter that sets a flag on the target
struct flag_setter_impl
{
    template<class X>
    void operator()(X* px) const {
        if (px) {
            assert(!px->flag);
            std::cout << "setting flag!" << std::endl;
            px->flag = true;
        }
    }
};
// a type of unique_ptr which does not delete, but sets a flag
template<class X>
struct flag_setter
{
    flag_setter(X* px) : px(px) {}
    flag_setter(const flag_setter&) = delete;
    flag_setter(flag_setter&& r) noexcept : px(r.px) { r.px = nullptr; }
    flag_setter& operator=(const flag_setter& r) = delete;
    flag_setter& operator=(flag_setter&& r)
    {
        flag_setter tmp(std::move(r));
        std::swap(tmp.px, px);
        return *this;
    }
    ~flag_setter() noexcept {
        flag_setter_impl()(px);
    }
private:
    X* px;
};
// make a flag_stter for x
template<class X>
auto make_flag_setter(X& x) -> flag_setter<X>
{
    return flag_setter<X>(&x);
}

// quick test
auto main() -> int
{
    using namespace std;
    X x;
    {
        auto fs1 = make_flag_setter(x);
        auto fs2 = move(fs1);
    }
    return 0;
}

目前还不能保证应用复制省略。有保证的拷贝省略被建议包含在C++17中。是否应用复制省略完全由编译器决定(不过,有些编译器可以选择完全禁用它)。

避免这种需要的一种潜在方法可能是使用一个基本上不可用的类型,该类型只能用作您感兴趣的类型的构造函数参数,并返回该类型的对象:

class FlagContainerBuilder {
    friend class FlagContainer;
public:
    FlagContainerBuilder(/* suitable arguments go here */);
    // nothing goes here
};
class FlagContainer {
    // ...
public:
    FlagContainer(FlagContainerBuilder&& builder);
    // as before
};
FlagContainerBuilder createFlagContainer() { ... }

这样就可以避免可能销毁从createFlagContainer()返回的FlagContainer

我知道RVO主要是应用的,但我能指望它吗?

没有。编译器可以实现RVO,但不是必需的。只有当编译器承诺这样做时,你才能指望它。

尽管根据标准12.8/3/p311.复制和移动类对象[class.copy]将其呈现为编译器可以执行NRVO(也称为复制省略)的上下文,但你不能依赖它。依赖这种优化的程序实际上是不可移植的。

为了确保对象的移动,我将定义一个移动构造函数,在里面我将使另一个对象的指针为空,而在析构函数中,我将检查指针是否为nullptr,以便将其标志设置为true:

class FlagContainer {
public:
    FlagContainer(FlagContainer&& other) : someItem(other.someItem) { 
      other.someItem = nullptr; 
    }
    ~FlagContainer() {
        if(someItem) someItem->flag = true;
    }
    Item * someItem;
};  
FlagContainer createFlagContainer() {
    return FlagContainer();
}

实时演示