声明析构函数会影响对象的生存期?

时间:2018-10-01 16:33:37

标签: c++

15年后回到C ++并使用“发现现代C ++”一书中描述的表达模板概念,我遇到了一种我无法解释的行为(尽管我的C ++知识那时还很基础,所以我希望这很明显。)

这是最小的示例(我知道这很长,但这是我能尽力说明问题的最佳方法):

#include <iostream>
#include <iomanip>

template <typename T>
class container {
private:
    T data;

    template<typename Src>
    void copy_from(Src& that) {
        data = that.get();
    }

public:

    using value_type = T;

    inline T get() const {
        auto p= data;
        return p;
    }

    void set(T v) {
        data = v;
    }

    template<typename Src>
    container& operator=(const Src& that) {
        copy_from(that);
        return *this;
    }

    template<typename Src>
    container(const Src& that){
        copy_from(that);
    }

    container() = default;

    friend std::ostream& operator <<(std::ostream& s, container<T> const & matrix) {
        s << std::endl << std::fixed << std::setprecision(8) << matrix.data <<std::endl;
        return s;
    }
};

template <typename A1, typename A2>
class sum {
    using mytype = sum<A1, A2>;
public:
    sum(const A1 & a1, const A2 & a2):
    a1(a1), a2(a2) {
        std::cout <<"constructing sum ("<<(long)this<<") with a1 = " << (long)&a1 << " and a2 = " <<(long)&a2 << std::endl;
    }

    // (1)
    // ~sum() {}

    using value_type = std::common_type_t <typename A1::value_type, typename A2::value_type>;

    inline value_type get() const {
        std::cout <<"getting elem from sum ("<<(long)this<<") with a1 = " << (long)&a1 << " and a2 = " <<(long)&a2 << std::endl;

        auto x = a1.get();
        auto y = a2.get();
        auto p = x + y;
        return p;
    }

    void print() {
        std::cout <<"I'm a sum ("<<(long)this<<") with a1 = " << (long)&a1 << " and a2 = " <<(long)&a2 << std::endl;
    }

private:
    const A1 &a1;
    const A2 &a2;
};

template <typename A1, typename A2>
sum<A1, A2> inline operator+ (const A1& a1, const A2& a2) {
    return {a1, a2};
}

template <typename A>
class apply {

public:
    using value_type = typename A::value_type;
    using function_type = std::function<value_type(value_type)>;

    apply (const A& a, const function_type & f):
    a(a), f(f) {std::cout <<"constructing apply ("<<(long)this<<") with a = " << (long)&a<< std::endl; }

    inline value_type get() const {
        std::cout <<"address of apply's member obj is " << (long)&a << ", type is " <<typeid(a).name() << std::endl;
        auto p = f(a.get());
        return p;
    }

private:
    const A &a;
    const function_type & f;
};

template<typename T>
class applicator {
public:

    using value_type = T;
    using function_type = std::function<value_type(value_type)>;

    applicator( const function_type & f): f(f) { }

    template<typename A>
    // (2)
    inline apply<A> operator() (A param) {
        std::cout <<"address of () param is " << (long)&param << ", type is " <<typeid(param).name() <<": ";
        param.print();
        apply<A> op { param, f };
        return op;
    }

private:
    const function_type & f;
};

double square(double x) {
    return x*x;
}

int main() {

    std::cout << "--- Creating variable" << std::endl;
    container<double> W;
    std::cout << W;

    std::cout << "--- Setting values in the variable" << std::endl;
    W.set(4);

    std::function<double(double)> my_fun = square;
    applicator sq { my_fun };

    std::cout << "decltype(W) is_trivially_copyable? " << std::is_trivially_copyable_v<decltype(W)> << std::endl;
    std::cout << "decltype(W+W) is_trivially_copyable? " << std::is_trivially_copyable_v<decltype(W+W)> << std::endl;
    std::cout << "decltype(sq(W+W)) is_trivially_copyable? " << std::is_trivially_copyable_v<decltype(sq(W+W))> << std::endl;

    std::cout << "--- Performing function on addition" << std::endl;
    std::cout << std::hex;

    auto r = sq(W+W);

    std::cout << "Created var r with address " <<(long)&r<<", type: " <<typeid(r).name() <<std::endl;

    std::cout << "--- Copying to container and printing out results" << std::endl;
    std::cout << r << std::endl;

    return 0;
}

这是输出:

--- Creating variable

0.00000000
--- Setting values in the variable
decltype(W) is_trivially_copyable? 1
decltype(W+W) is_trivially_copyable? 1
decltype(sq(W+W)) is_trivially_copyable? 1
--- Performing function on addition
constructing sum (7ffeefbff3a0) with a1 = 7ffeefbff508 and a2 = 7ffeefbff508
address of () param is 7ffeefbff388, type is 3sumI9containerIdES1_E: I'm a sum (7ffeefbff388) with a1 = 7ffeefbff508 and a2 = 7ffeefbff508
constructing apply (7ffeefbff398) with a = 7ffeefbff388
Created var r with address 7ffeefbff4e0, type: 5applyI3sumI9containerIdES2_EE
--- Copying to container and printing out results
address of apply's member obj is 7ffeefbff388, type is 3sumI9containerIdES1_E
getting elem from sum (7ffeefbff388) with a1 = 7ffeefbff4c8 and a2 = 7ffeefbff3b0

0.00000000

// (2)中的参数是按值传递的,因此,当operator()存在时,临时对象将被销毁,因此a类的apply成员将引用垃圾。这对我来说很有意义。但是,如果我们取消注释类sum中的析构函数定义(请参阅// (1)),那么结果是正确的,并且最终的sum对象引用正确的container。为什么?

如果我们保留析构函数的注释,并将// (2)更改为通过引用传递,那么一切似乎都可以正常工作。是否因为const引用延长了operator+返回的临时对象的生存期?如果是这样,为什么apply<A>构造函数不延长param的{​​{1}}对象的生存期? apply类保留对在构造函数中传递的对象的引用。

1 个答案:

答案 0 :(得分:3)

  

// (2)中的参数是按值传递的,因此,当operator()存在时,临时对象将被销毁,因此a类的apply成员将引用垃圾。这对我来说很有意义。

正确。在

inline apply<A> operator() (A param)

返回的apply<A>具有对param的引用,该引用超出了范围,因此您有一个悬空引用,使用它是未定义的行为。

  

但是,如果我们取消注释类sum中的析构函数定义(请参阅// (1)),那么结果是正确的,并且最后的sum对象引用了正确的container。为什么?

再次,未定义的行为。仅仅因为代码不起作用并不意味着就不能。由于您的行为不确定,甚至可以为您提供“正确的”结果。

  

如果我们保留析构函数的注释,并将// (2)更改为通过引用传递,那么一切似乎都可以正常工作。是否因为const引用延长了operator+返回的临时对象的生存期?

这是因为您已将引用绑定到呼叫站点中的对象。这意味着当函数返回到调用站点时,其引用仍然引用您传递给它的有效对象。这意味着您的引用仍然指向有效的对象,并且您已定义行为。

  

如果是这样,为什么apply<A>构造函数不延长param的{​​{1}}对象的生存期? apply类保留对在构造函数中传递的对象的引用。

operator()仅延长了函数本地临时生存期。

const &

以上是合法的,编译器会将返回值的生存期延长到范围的末尾。具有

{ // start of some scope
    const int& foo = function_that_returns_temporary();
} // end of some scope

不会延长struct Foo { const int& bar Foo(const int& ref) : bar(ref) }; 所指对象的寿命。如果ref所指的对象超出范围,而用它创建的ref对象则被销毁,则该对象将留下一个悬挂的引用。