C++中的Zip类(内部对象生存期)

uqcuzwp8  于 2023-03-25  发布在  其他
关注(0)|答案(3)|浏览(115)

我在C++中有下面的Zip类,它的工作原理与Python的zip相同。
当我运行下面的代码时,我得到这样的输出:
在第二种情况下,我额外创建了一个临时的std::vector<int>{7, 8, 9},似乎这个向量的生命周期在Zip构造函数退出时就结束了。这就是为什么我们看到错误的值06.91092e-317
我想知道为什么会这样。因为内部Zip将其元素存储为const Ts&。所以一个const引用。这不应该延长临时向量的生命周期,直到父Zip对象被析构(=在for循环之后)吗?有什么方法可以解决这个问题吗?
示例代码,https://godbolt.org/z/4n6j83fn5

#include <tuple>
#include <vector>
#include <iostream>

template<class... Ts>
class Zip
{
public:
    explicit Zip(const Ts&... objs)
        : m_data(objs...) { }

    struct ZipIterator
    {
    public:
        explicit ZipIterator(const std::tuple<const Ts&...>& data, std::size_t idx)
            : m_data(data), m_idx(idx) { }

        ZipIterator& operator++()
        {
            ++m_idx;
            return *this;
        }

        bool operator!=(const ZipIterator& rhs) const
        {
            return m_idx != rhs.m_idx;
        }

        auto operator*() const
        {
            return std::apply([this](auto const&... obj) { return std::forward_as_tuple(obj.at(m_idx)...); }, m_data);
        }

    private:
        const std::tuple<const Ts&...>& m_data;
        std::size_t m_idx;
    };

    ZipIterator begin() const
    {
        return ZipIterator(m_data, 0);
    }

    ZipIterator end() const
    {
        return ZipIterator(m_data, std::get<0>(m_data).size());
    }

private:
    std::tuple<const Ts&...> m_data;
};

int main()
{
    const std::vector<double> vec1{1,  2,  3};
    const std::vector<double> vec2{11, 22, 33};

    for (const auto& [v1, v2] : Zip(vec1, vec2))
    {
        std::cout << v1 << " | " << v2 << std::endl;
    }
    std::cout << std::endl;

    for (const auto& [v1, v2, v3] : Zip(vec1, vec2, std::vector<double>{7, 8, 9}))
    {
        std::cout << v1 << " | " << v2 << " | " << v3 << std::endl;
    }

    return 0;
}
ssgvzors

ssgvzors1#

@BoP完美地回答了你关于“为什么”的问题。这个答案为你的问题提供了一个可能的解决方案。
修复涉及到一个MaybeOwning类,如果它是从非临时构造的,则通过引用存储,如果它是从临时构造的,则通过值存储。要使其工作,您需要将构造函数 Package 在工厂函数zip中,该工厂函数正确转发正确的类型。这就是为什么在此实现中Zip的构造函数是私有的。

#include <tuple>
#include <vector>
#include <iostream>

template <typename E>
struct MaybeOwning
{};

template <typename E>
struct MaybeOwning<E&&>
{
    MaybeOwning(E&& e) : _val(e) {}
    E& get() { return _val; };
    E const& get() const { return _val; };
    E _val;
};

template <typename E>
struct MaybeOwning<E&>
{
    MaybeOwning(E& e) : _val(e) {}
    E& get() { return _val; };
    E const& get() const { return _val; };
    E& _val;
};

template<class... Ts>
class Zip
{
    template <typename... Args>
    friend auto zip(Args&&...);

private:
    explicit Zip(Ts&&... objs)
        : m_data(MaybeOwning<Ts>(std::forward<Ts>(objs))...) { }

public:
    struct ZipIterator
    {
    public:
        explicit ZipIterator(const std::tuple<MaybeOwning<Ts>...>& data, std::size_t idx)
            : m_data(data), m_idx(idx) { }

        ZipIterator& operator++()
        {
            ++m_idx;
            return *this;
        }

        bool operator!=(const ZipIterator& rhs) const
        {
            return m_idx != rhs.m_idx;
        }

        auto operator*() const
        {
            return std::apply([this](auto const&... obj) { return std::forward_as_tuple(obj.get().at(m_idx)...); }, m_data);
        }

    private:
        const std::tuple<MaybeOwning<Ts>...>& m_data;
        std::size_t m_idx;
    };

    ZipIterator begin() const
    {
        return ZipIterator(m_data, 0);
    }

    ZipIterator end() const
    {
        return ZipIterator(m_data, std::get<0>(m_data).get().size());
    }

private:
    std::tuple<MaybeOwning<Ts>...> m_data;
};

template <typename... Ts>
auto zip(Ts&&... ts){
    return Zip<Ts&&...>(std::forward<Ts>(ts)...);
}

int main()
{
    const std::vector<double> vec1{1,  2,  3};
    const std::vector<double> vec2{11, 22, 33};

    for (const auto& [v1, v2] : zip(vec1, vec2))
    {
        std::cout << v1 << " | " << v2 << std::endl;
    }
    std::cout << std::endl;

    for (const auto& [v1, v2, v3] : zip(vec1, vec2, std::vector<double>{7, 8, 9}))
    {
        std::cout << v1 << " | " << v2 << " | " << v3 << std::endl;
    }

    return 0;
}

https://godbolt.org/z/Tsa8W31v7

bbuxkriu

bbuxkriu2#

explicit Zip(const Ts&... objs)
    : m_data(objs...) { }

生存期扩展仅适用于直接绑定,临时绑定到objs参数。
生存期延长不会传输到m_data成员。

aiazj4mn

aiazj4mn3#

这里有一个基于@joergbrech的答案的补充。使用class template argument deduction代替 Package 器函数。

template<class... Ts>
class Zip
{
public:
    template <typename... T>
    explicit Zip(T&&... objs)
        : m_data(MaybeOwning<T&&>(std::forward<T>(objs))...) {}
};

template <typename... Ts> Zip(Ts&&...) -> Zip<Ts&&...>;

Demo

相关问题