如何使右值在 c++ 中表现得像左值引用?

How to make rvalue behave like lvalue reference in c++?

提问人:yomol777 提问时间:4/28/2023 最后编辑:Remy Lebeauyomol777 更新时间:4/29/2023 访问量:104

问:

我目前正在用C++编写一个简单的代数库。该库有一个类,定义如下:Matrix

template<typename T>
class Matrix {
private:
    size_t n, m;
    T **const tab;

public:
    Matrix(const size_t n, const size_t m);
    Matrix(const Matrix&);
    ~Matrix(void);

    RowVector<T> operator[](const int i);
    const RowVector<T> operator[](const int i) const;

    ...
};

该代码很智能,它不仅允许您访问矩阵的某些特定元素(例如),而且还允许您用作向量。M[2][3]M[2]

但是,作为优化,数据不会复制到向量中。 通过指向内存内部的 .如果调用每次都会构造一个新的向量,那么访问一个矩阵元素将是非常无效的。Vectoroperator[]Matrixoperator[]

这也使得分配矩阵元素成为可能,如下所示:

M[5][7] = 12;

但是,我对这个解决方案有两个问题:

  1. 返回值 是右值,不能像这样设置矩阵的整行:operator[]
Matrix M(2, 3);
RowVector v(3);

M[0] = v;
  1. 当我输入这样的内容时:
v = M[7];

我想调用 's copy 构造函数,而不是 move 构造函数。但是 的返回值是一个右值,编译器将调用它的移动构造函数。RowVectoroperator[]

综上所述:

RowVector是一个右值,但它包含指向 的内部数据的指针,我希望将其视为左值引用。Matrix

这在 C++ 中可能吗?如果没有,有人可以建议对此代码进行一些重构以实现我想要的吗?

编辑:

RowVector声明:

template<typename T>
class Vector {
protected:
    const size_t len;
 
    T *const tab;
    bool malloced;
 
public:
    Vector(const size_t len);
    Vector(T *const tab, const size_t len);
    virtual ~Vector(void);
 
    T &operator[](const int i);
    const T &operator[](const int i) const;
};
 
template<typename T>
class RowVector : public Vector<T> {
public:
    RowVector(const size_t len);
    RowVector(T *const tab, const size_t len);
    ~RowVector(void) override;
 
    template<typename U> friend
    std::ostream &operator<<(std::ostream&,
                 const RowVector<U>&);
};

Vector定义:

template<typename T>
Vector<T>::Vector(const size_t len) :
    len(len),
    tab(new T[len]),
    malloced(true)
{
}
 
template<typename T>
Vector<T>::Vector(T *const tab, const size_t len) :
    len(len),
    tab(tab),
    malloced(false)
{
}
 
template<typename T>
Vector<T>::~Vector(void)
{
    if (malloced)
        delete tab;
}
 
template<typename T>
T &Vector<T>::operator[](const int i)
{
    return tab[i];
}
 
template<typename T>
const T &Vector<T>::operator[](const int i) const
{
    return tab[i];
}

Matrix<T>::operator[]():

template<typename T>
RowVector<T> Matrix<T>::operator[](int i)
{
    return RowVector(tab[i], m);
}

template<typename T>
const RowVector<T> Matrix<T>::operator[](int i) const
{
    return RowVector(tab[i], m);
}
C++ 构造函数 move-constructor

评论

0赞 Botje 4/28/2023
如果不看到您希望实现的完整定义和一整套用例,这将很难回答。另外,为什么“调用移动构造函数”是一个问题?RowVector<T>
3赞 StoryTeller - Unslander Monica 4/28/2023
你的问题不在于右值和左值。您将一种类型用于多个目的,因此很难将所有事情都做对。我会分担责任。具有 和 be 单独的类型,将允许您以更精细的方式重载。RowReferenceRowVector
0赞 yomol777 4/28/2023
@Botje 下面是 RowVector 类的定义:pastebin.com/6yYJ8hST 。这里的声明:pastebin.com/9zXqGBEN
1赞 Botje 4/28/2023
编辑您的问题,而不是链接到第三方网站。
0赞 Botje 4/28/2023
这不是一个最小的可重现示例。的定义在哪里?的定义在哪里?RowVector<T>::operator=Matrix<T>::operator[]

答:

1赞 alexnavtt 4/29/2023 #1

首先,我同意@StoryTeller,最好将其拆分为 RowVector 和 RowReference 类。但是,我仍然会继续回答您最初的问题。

好的,让我们逐个回顾一下。

  1. operator[] 的返回值是一个右值,不能像这样设置矩阵的整行:
Matrix M(2, 3);
RowVector v(3);

M[0] = v;

这实际上不是这里的问题。如果函数是类类型,则将其分配给函数的临时返回值是完全有效的。但是,您可能遇到问题的原因是 RowVector 类定义中有 const 成员,即 和 。在 C++ 中,默认赋值运算符等效于说类似tablen=

FOR member IN this
    member = other.member
END

考虑到这一点,尝试这样做是无效的,因为 len 是 .因此,C++ 禁用任何具有成员的类的赋值运算符。如果您想定义自己的赋值运算符,您可以完全自由地这样做。我想你会看到类似的东西len = other.lenconstconst

RowVector<T>& operator=(const RowVector<T>& other){
    if (len != other.len)
        throw std::runtime_error("Cannot assign rows with different lengths!");
    std::copy(other.tab, other.tab + len, tab);
    return *this
}

这应该允许您设置整行。

  1. 当我输入这样的内容时:
v = M[7];

我希望调用 RowVector 的复制构造函数,而不是移动构造函数。但是 operator[] 的返回值是一个右值,编译器将调用它的移动构造函数。

我不相信移动构造函数会在这里发挥作用。对于定义自定义复制构造函数、复制赋值运算符、移动构造函数、移动赋值运算符或析构函数的任何类,都会自动禁用默认移动构造函数。由于 RowVector 定义了自定义析构函数,因此不应定义默认的移动构造函数,而且看起来您没有自己定义一个。这个问题可能又回到了赋值运算符的删除,这将再次通过定义自定义赋值运算符来解决。

稍微切线一点,将矩阵数据存储为双指针并不是创建矩阵类的最有效方法。如果你追求效率,那么你总是希望以连续的内存分配为目标,在这种情况下,这将是 n x m 的大小,并且你会在 .当然,这也会搞砸你现有的向量类,所以如果你想走这条路,我就把它留给你了。T**std::vectoroperator[]