2015-07-06 3 views
1

Я пытаюсь написать векторный класс C++, который хранит массив данных и позволяет выполнять математические операции поэтапно. Я хочу реализовать это так, что выражение a = b + c + d должно циклически перебирать все элементы только один раз и напрямую записывать сумму b[i] + c[i] + d[i] в a[i] без создания промежуточных векторов.Эффективные векторные операторы в C++/Ссылки на временные объекты

я писал что-то вроде этого:

template<class T, int N> 
class VectorExpression { 
    public: 
    virtual T operator[] (int i) const = 0; 

    virtual ~VectorExpression() {} 
} 

template<class T, int N> 
class MyVector : public VectorExpression<T, N> { 
    T data[N]; 

    public: 
    T& operator[] (int i) { return data[i]; } 
    T& const operator[] (int i) const { return data[i]; } 

    MyVector<T,N>& operator=(const VectorExpression<T,N> &rhs) { 
     for (int i = 0; i < N; ++i) 
     data[i] = rhs[i]; 

     return *this; 
    } 
} 

template<class T, int N> 
class VectorSum : public VectorExpression<T, N> { 
    VectorExpression<T,N> &a, &b; 

    public: 
    VectorSum(VectorExpression<T,N> &aa, VectorExpression<T,N> &bb) 
    : a(aa), b(bb) {} 

    T operator[] (int i) const { return a[i] + b[i]; } 
} 

template<class T, int N> 
VectorSum<T,N> operator+(const VectorExpression<T,N> &a, 
     const VectorExpression<T,N> &b) 
{ 
    return VectorSum<T,N>(a, b); 
} 

int main() { 
    MyVector<double,10> a, b, c, d; 

    // Initialize b, c, d here 

    a = b + c + d; 

    return 0; 
} 

Вероятно, эта функциональность обеспечивается классом valarray, но это потому, что я пытался лишить его до минимального примера.

Я сделал operator[] виртуальный, потому что это позволяет вложенности все виды выражений (например a = !(-b*c + d)) при условии, я бы определить все операторы и соответствующие классы, аналогичные VectorSum.

Я использую ссылки, потому что обычные переменные не являются полиморфными, а указатели не работают с перегрузкой оператора.

Теперь мои вопросы по этому поводу являются:

  • В заявлении a = b + c + d; два временных VectorSum<double,10> объекты будут созданы для хранения b + c и (b+c) + d соответственно. Будут ли они жить достаточно долго, чтобы заставить полиморфное поведение работать? Более конкретно, (b+c) + d сохранит ссылку на b + c, но будет ли этот объект еще существовать, когда вызывается operator=? Согласно this post все временные файлы должны существовать до тех пор, пока operator= не вернется, но это также относится к более старым версиям C++?

  • Если нет, то как это делается? Единственная альтернатива, которую я вижу, заключается в том, чтобы выделить объекты VectorSum с использованием new, вернуть их по ссылке, а затем удалить их в функциях operator=, но это кажется немного громоздким и, вероятно, намного менее эффективным. Я также не уверен, что это всегда безопасно.

  • (Minor вопрос) Можно ли переопределить тип возвращаемого T из VectorExpression::operator[] по T& const в MyVector?

EDIT

Я имел неправильные типы аргументов в операторе +: изменил их от VectorSum к VectorExpression.

+1

Это не отвечает на ваши вопросы, и я не стал глубоко заглядывать в ваш код, но я не уверен, что это способ добиться эффективной оценки вашей суммы. Рассматривали ли вы использование [шаблонов выражения] (https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Expression-template)? – coincoin

+0

Насколько я понимаю, сайт, на который вы ссылаетесь, делает более или менее то же самое, что и я, за исключением использования шаблонов вместо наследования. Я не думал об этом, так что спасибо. Я не очень уверен, какой вариант лучше; их методу не нужны виртуальные классы, но для этого требуется гораздо больше классов (особенно если вы считаете их после создания экземпляра), а временные объекты всех этих классов должны быть созданы. – PieterNuyts

+1

Это может быть интересно вам: http://stackoverflow.com/q/11809052/1116364 –

ответ

0

Ну вот что я придумал:

#include <iostream> 
#include <initializer_list> 
#include <algorithm> 


template<class T, int N> 
class VectorExpression { 
public: 
    virtual T operator[] (int i) = 0; 
    virtual const T operator[] (int i) const = 0; 

    virtual ~VectorExpression() {} 
}; 


template<class T, int N> 
class MyVector : public VectorExpression<T, N> { 
    T data[N]; 

public: 
    MyVector() { 
    // initialize zero 
    std::fill(std::begin(data), std::end(data), T()); 
    } 

    MyVector(const std::initializer_list<T>& values) { 
    // initialize from array initializer_list 
    std::copy(std::begin(values), std::end(values), data); 
    } 

    MyVector(const VectorExpression<T,N>& rhs) { 
    for (int i = 0; i < N; ++i) 
     data[i] = rhs[i]; 
    } 

    MyVector<T,N>& operator=(const VectorExpression<T,N>& rhs) { 
    for (int i = 0; i < N; ++i) 
     data[i] = rhs[i]; 

    return *this; 
    } 

    T operator[] (int i) { return data[i]; } 
    const T operator[] (int i) const { return data[i]; } 

    friend std::ostream& operator<<(std::ostream& stream, MyVector& obj) { 
    stream << "["; 
    for (int i = 0; i < N; ++i) { 
     stream << obj.data[i] << ", "; 
    } 
    stream << "]"; 

    return stream; 
    } 
}; 

template<class T, int N> 
class VectorSum : public VectorExpression<T, N> { 
    const MyVector<T,N> &a, &b; 

public: 
    VectorSum(const MyVector<T,N>& aa, const MyVector<T,N>& bb): 
    a(aa), b(bb) { 
    } 

    T operator[] (int i) { return return a[i] + b[i]; } 

    const T operator[] (int i) const { return a[i] + b[i]; } 
}; 


template<class T, int N> 
MyVector<T,N> operator+(const MyVector<T,N>& a, const MyVector<T,N>& b) { 
    return VectorSum<T,N>(a, b); 
} 


int main() { 
    MyVector<double,3> a, b({1,2,3}), c({3,4,5}), d({4,5,6}); 

    a = b + c + d; 

    std::cout << b << std::endl; 
    std::cout << c << std::endl; 
    std::cout << d << std::endl; 
    std::cout << "Result:\n" << a << std::endl; 

    return 0; 
} 

Выход:

[1, 2, 3, ] 
[3, 4, 5, ] 
[4, 5, 6, ] 
Result: 
[8, 11, 14, ] 

Я добавил конструктора и ostream операторы initializer_list (C++ 11) исключителен для удобства целей/иллюстраций ,

Поскольку вы определили оператор [] как возвращаемый по значению, мне не удалось установить элементы в массиве данных для тестирования (поскольку ошибка: lvalue требуется как левый операнд присвоения); обычно этот оператор должен быть ссылкой - но тогда в вашем случае VectorSum :: operator [] не будет работать, потому что это не скомпилирует из-за возврата ссылки на временную.

Я также добавил конструктор копирования, потому что ...

// this calls MyVector's copy constructor when assigned to 'main::a' 
template<class T, int N> 
MyVector<T,N> operator+(const MyVector<T,N>& a, const MyVector<T,N>& b) { 
    return VectorSum<T,N>(a, b); // implicit MyVector::copy constructor 
} 

// this also calls MyVector's copy constructor (unless the copy constructor is defined explicit) 
template<class T, int N> 
MyVector<T,N> operator+(const MyVector<T,N>& a, const MyVector<T,N>& b) { 
    MyVector<T,N> res = VectorSum<T,N>(a, b); 
    return res; 
} 

// but this would call MyVector's assignment operator 
template<class T, int N> 
MyVector<T,N> operator+(const MyVector<T,N>& a, const MyVector<T,N>& b) { 
    MyVector<T,N> res; 
    res = VectorSum<T,N>(a, b); 
    return res; 
} 

В ответ на вопросы:

  1. Да - как бы он себя, если вы явно определили переменную и вернулся, что? Это то же поведение для временных, за исключением того, что не является объявлением переменной;
  2. n/a
  3. Я коснулся этого выше - вы не можете использовать ссылку на ссылку «возврат к временной ошибке» ; Однако нет причин, по которым вы можете добавить T & оператор [] в MyVector (т. Е. Не переопределять).

EDIT: ответы на комментарии:

  1. Функция спецификации должны быть идентичны при переопределении включая тип возвращаемого значения. Поскольку вы определили, что он возвращает значение в VectorExpression, он должен быть возвращен по значению в MyVector. Если вы попытаетесь изменить его на ссылку в дочернем классе, вы получите ошибку компиляции: указан конфликтный тип возвращаемого типа. Таким образом, вы не можете переопределить оператор const с версией, которая возвращает const T & вместо T. Кроме того, он должен вернуться к значению, так как MyVectorSum возвращает { a[i] + b[i] }, который будет временным, и вы не сможете вернуть ссылку на временный.

  2. Извините, моя ошибка исправлена ​​выше.

  3. , потому что:

    • MyVector не подтипом VectorSum - ошибка компиляции 'MyVector' не является производным от 'сопзЬ VectorSum'
    • Я также попытался с VectorExpression, но ошибка компиляции: ' не может выделить объект абстрактного типа '- потому что он пытается вернуть значение
    • Я выбрал MyVector, так как это тип ожидаемого результата. Да, это все те, что для циклов, но я не вижу способа обойти это: есть три переменные «массива данных», каждая из которых должна быть повторена для накопления. В какой-то момент кода вам придется делать циклы for.
  4. Понял, да, я смутился. удален из сообщения.

+0

1) Я не определял 'MyVector :: operator []' как return-by-value, поэтому в моем примере вы можете поместить элементы в вектор. Я делал это только в VectorExpression и VectorSum, потому что вы не можете назначать выражение вообще. У них есть только 'operator [] (int) const'; в 'MyVector' я добавил неперехватный' operator [] (int) '. Мой вопрос состоял в том, можно ли переопределить 'operator [] (int) const' версию, которая возвращает' const T & 'вместо' T'. – PieterNuyts

+0

2) Я не вижу смысла вашего добавления 'T operator [] (int i) {return 0; } ': Теперь, если объект VectorExpression окажется неконстантным, он ошибочно вернет 0. Так как вы все равно не возвращаетесь по ссылке, я не понимаю, почему вы не пропустили этот метод? – PieterNuyts

+0

3) Интересно, почему вы изменили тип возврата 'operator +' на 'MyVector '. Я могу ошибаться, но, похоже, это разрушает весь смысл наличия эффективного оператора, который оценивает в одном for-loop? Теперь вы оцените «b + c» и сохраните его в «MyVector», затем создайте еще один «MyVector» для хранения суммы этого объекта и 'd', а затем скопируйте его в' a', поэтому у нас есть три for-loops now ... – PieterNuyts

0

Я не думаю, что это на первый, но имеющие виртуальный operator[] метод, вероятно, убивает эффективность я пытался достичь, избегая 3 для петель и промежуточного хранения векторных размера временных.Создание виртуального метода не позволяет ему быть встроенным, что означает, что он должен быть фактически вызван как функция каждый раз, когда к элементу обращаются.

Основываясь на ссылках, которые я получил от людей, которые прокомментировали мой вопрос и от Google, я получил следующее решение, которое позволяет избежать необходимости в каких-либо виртуальных методах.

template<class T, int N, class V> 
class VectorExpressionBase { 
    V ref; 

protected: 
    explicit VectorExpressionBase(const V *ref) 
    : ref(const_cast<V*>(ref)) {} 

public: 
    T operator[] (int i) const { return ref[i]; } 
    T& operator[] (int i)  { return ref[i]; } 
} 

template<class T, int N> 
class VectorExpressionBase<T,N,void> { 
    T data[N]; 

protected: 
    explicit VectorExpressionBase(const void*) { 
     // Argument is unused but accepted to have uniform 
     // calling syntax 
    } 

public: 
    T operator[] (int i) const { return data[i]; } 
    T& operator[] (int i)  { return data[i]; } 
} 

template<class T, int N, class V> 
class VectorExpression : public VectorExpressionBase<T,N,V> { 
public: 
    template<class V1> 
    VectorExpression<T,N,V>& operator= (
     const VectorExpression<T,N,V1> &rhs) 
    { 
     for (int i = 0; i < N; ++i) 
      data[i] = rhs[i]; 

     return *this; 
    } 

    explicit VectorExpression(const V *ref = 0) 
    : VectorExpressionBase<T,N,V>(ref) {} 

    // Can define all kinds of operators and functions here such as 
    // +=, *=, unary + and -, max(), min(), sin(), cos(), ... 
    // They would automatically apply to MyVector objects and to the 
    // results of other operators and functions 
}; 

template<class T, int N> 
class MyVector : public VectorExpression<T,N,void> { 
} 

template<class T, int N, class VA, class VB> 
class VectorSum { 
    VectorExpression<T,N,VA> &a; 
    VectorExpression<T,N,VB> &b; 

    public: 
    VectorSum(VectorExpression<T,N,VA> &aa, VectorExpression<T,N,VB> &bb) 
    : a(aa), b(bb) {} 

    T operator[] (int i) const { return a[i] + b[i]; } 
} 

template<class T, int N, class VA, class VB> 
VectorExpr<T,N,VectorSum<T,N,VA,VB> > 
operator+(const VectorExpression<T,N,VA> &a, 
      const VectorExpression<T,N,VB> &b) 
{ 
    VectorSum<T,N,VA,VB> sum(a, b); 
    return VectorExpr<T,N,VectorSum<T,N,VA,VB> >(sum); 
} 

класса VectorExpression теперь просто оборачивает класс, который делает работу (в данном случае VectorSum. Это позволяет определить все виды функций и операторов для VectorExpression только, вместо того, чтобы перегружать их VectorSum, VectorProduct и т.д.

MyVector вытекает из специального случая VectorExpression, который имеет специализированный базовый класс, это на самом деле не нужно, но это хорошо, потому что это делает все функции и оператор, определенные для VectorExpression также доступны для MyVector используя простой базовый CLAS. s VectorExpressionBase, который имеет дело только с хранилищем и оператором [], все другие операторы и методы не должны дублироваться в специализации для V = void.

Пользователи должны знать только классы MyVector<T,N> (для хранения данных) и, возможно, около VectorExpression<T,N,V>, если они хотят определить дополнительные функции и операторы. VectorExpressionBase и классы, подобные VectorSum, не должны быть видимыми для внешнего мира.

Я считаю свое первоначальное решение несколько более понятным концептуально, потому что смысл каждого класса более понятен и потому что он не требует параметра шаблона V, но этот более эффективен, поскольку он не требует каких-либо виртуальных функций, которые вероятно, может иметь большое значение в некоторых случаях.

Благодарим за то, что указали мне нужные ссылки!

P.S. Конечно, большинство/все это не ново, но я подумал, что было бы неплохо подвести итоги и объяснить это немного. Надеюсь, это поможет другим.

РЕДАКТИРОВАТЬ

Я изменил тип элемента данных VectorExpressionBase<T,N,V>::ref от V& к V. Это необходимо, так как временный объект V, на который указывает ссылка, может больше не существовать во время оценки VectorExpression. Например, временный объект VectorSum прекращает свое существование, когда возвращается функция operator+, что делает возвращаемый объект VectorExpression бесполезным.

Я также завершил код с некоторыми конструкторами и исправил функцию operator+.

+1

Скорее всего, ваши виртуальные функции являются [devirtualized] (http://stackoverflow.com/questions/7046739/lto-devirtualization-and-virtual-tables) компилятором и на самом деле не являются проблемой производительности. – nwp

+0

Спасибо, хорошая точка! Я не понимал, что это возможно. – PieterNuyts

+0

Но я предполагаю, что каждый объект MyVector будет по-прежнему содержать указатель vtable, так как нет возможности гарантировать, что * все * функции могут * всегда * быть девиртуализированы? Для программ, которые используют много маленьких векторов, это может быть серьезной накладной памятью. Или я чего-то не хватает? – PieterNuyts

Смежные вопросы