Лучший способ изменить частный вектор в классе

У меня есть вопрос о лучшей практике программирования. Скажем, у меня есть следующий класс:

class MyClass {
public: 
    vector<double> data() { return data_; }
    void set_data(int index, double value) { data_[index] = value; }
private:
    vector<double> data_;
};

У меня есть частный вектор data_ и функции доступа/мутатора. Использование operator= в случае публичного data_ очень удобно. Однако мне нужно сделать data_ закрытым и получить доступ/изменить его через функции-члены. Но я не уверен, как лучше построить функцию-мутатор. Лучший способ, который у меня есть до сих пор, - это тот, что в примере. Какие-либо предложения?


person devotee    schedule 11.05.2014    source источник
comment
Я не понимаю, почему тот, что в примере, НЕ ХОРОШИЙ ... одним небольшим улучшением было бы сделать мутатор в структуре просто прототипом и иметь функцию ниже в программе.   -  person explodingcreeperssss    schedule 12.05.2014
comment
больше контекста не помешало бы. учитывая только это, лучшим подходом было бы сказать using MyClass = std::vector<double>;   -  person Ryan Haining    schedule 12.05.2014
comment
Ваш класс в том виде, в котором он написан, так же хорош, как автономный std::vector<double>, и ничего не говорит нам о том, чего вы пытаетесь достичь. Это затрудняет совет о лучшем способе.   -  person jrok    schedule 12.05.2014
comment
Не делайте уроки только ради этого. Каждый класс должен иметь одну единственную, четкую цель и ответственность. Если в вашем классе слишком много обязанностей, разделите их. Если его нет, удалите его.   -  person Kerrek SB    schedule 12.05.2014
comment
В нынешнем виде ваш метод data() должен быть const, и у вас нет возможности установить размер вашего вектора. Как вы хотите создать этот класс?   -  person Chris Drew    schedule 12.05.2014


Ответы (2)


Это зависит от того, что вы хотите делать с классом, но я бы предложил:

double getAt(size_t index) const { return data_.at(index); }
void setAt(size_t index, double value) { data_.at(index) = value; }

Это имеет то преимущество, что вы можете изменить внутреннюю реализацию класса, например, использовать другой контейнер STL, и вам не нужно будет менять интерфейс. Обратите внимание, что получателем является const.

Если производительность критична и вы доверяете вызывающим объектам, вы можете не проверять границы:

double getAt(size_t index) const { return data_[index]; }
void setAt(size_t index, double value) { data_[index] = value; }

Но может быть полезно иметь возможность установить весь вектор, и вы не возражаете против раскрытия реализации, и в этом случае вам может понадобиться что-то вроде:

void setData(std::vector<double> data) { data_ = std::move(data); }

Обратите внимание, что setData берет вектор по значению и перемещает его в закрытую переменную-член, это означает, что если вызывающий объект передает rvalue, то копирование не производится.

Если вам нужно получить весь вектор, у вас есть выбор. Если вектор не слишком велик, производительность не критична или вам все равно нужно сделать копию, тогда возврат по значению подойдет и будет моим первым выбором:

std::vector<double> getData() const { return data_; }

В противном случае возврат по ссылке:

const std::vector<double>& getData() const { return data_; }
std::vector<double>& getData() { return data_ } 

Но имейте в виду, что это полностью нарушает инкапсуляцию, особенно неконстантную версию.

person Chris Drew    schedule 11.05.2014

Простейшие способы предоставления доступа к данным.

double& at(size_t index) { return data_[index]; }
double const& at(size_t index) const { return data_[index]; }
person R Sahu    schedule 11.05.2014
comment
Это выглядит слишком похоже на возвращение дескрипторов внутренних объектов для меня. - person Chris Drew; 12.05.2014
comment
@ChrisDrew Это почти правда. Единственное отличие состоит в том, что вы можете изменить внутренний держатель данных на массив double и по-прежнему иметь возможность поддерживать интерфейс. Если вы сошли с ума, вы могли бы даже изменить внутренние данные на std::map и по-прежнему иметь возможность поддерживать этот интерфейс.1 - person R Sahu; 12.05.2014
comment
@R Sahu: Верно, но инкапсуляция - это нечто большее, чем просто это. Мы также хотим защитить личные данные. Скотт Мейерс говорит, что проблема со вторым методом заключается в том, что он может привести к «висячим дескрипторам», см. ideone.com/McIGCp. Далее Скотт признает, что operator[] в vector возвращает ссылку на данные в контейнере, но... такие функции являются исключением, а не правилом. - person Chris Drew; 12.05.2014
comment
@ChrisDrew Все контейнеры STL возвращают ссылки на содержащиеся в них данные. Понятно, что это вопросы мнения. Риск висячих указателей является неотъемлемой частью программирования на C/C++. Вы должны знать потенциальные ловушки и использовать интерфейс своего класса с учетом связанных с ними рисков. Возврат объектов по значению отлично работает для базовых типов и не слишком больших POD. Они ломаются, когда у вас есть контейнеры и сложные совокупные объекты. - person R Sahu; 12.05.2014
comment
@R Sahu: Но вам не ставили задачу написать общий контейнер STL. В этом случае вы возвращаете только double, так почему бы не вернуть его по значению? Это будет так же быстро и менее подвержено ошибкам. - person Chris Drew; 12.05.2014
comment
@R Саху, но согласился, что это вопрос мнения, я могу отредактировать свой ответ, чтобы прояснить последствия возврата всего вектора по значению для производительности. - person Chris Drew; 12.05.2014