Геттер и сеттер, указатели или ссылки, и хороший синтаксис для использования в C++?

Я хотел бы знать хороший синтаксис для геттеров и сеттеров C++.

private:
YourClass *pMember;

сеттер легко, я думаю:

void Member(YourClass *value){
  this->pMember = value; // forget about deleting etc
}

и добытчик? должен ли я использовать ссылки или указатели const?

пример:

YourClass &Member(){
   return *this->pMember;
}

или

YourClass *Member() const{
  return this->member;
}

в чем разница между ними?

спасибо,

Джо

EDIT:

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

поэтому я думаю, что буду использовать указатели const вместо ссылок

указатели const не могут быть удалены или установлены, верно?

8 ответов


как общий закон:

  • Если NULL является допустимым параметром или возвращаемым значением, используйте указатели.
  • Если NULL не допустимый параметр или возвращаемое значение, использовать ссылки.

поэтому, если сеттер должен быть вызван с NULL, используйте указатель в качестве параметра. В противном случае используйте ссылку.

Если допустимо вызывать геттер объекта, содержащего нулевой указатель, он должен возвращать указатель. Если такой случай является незаконным инвариант, возвращаемое значение должно быть ссылкой. Затем геттер должен выдать исключение, если переменная-член имеет значение NULL.


лучше всего предоставить реальный интерфейс OO клиенту, который скрывает детали реализации. Геттеры и сеттеры-это не ОО.


ваш код выглядит так, как будто вы привыкли к другому языку - в C++ с помощью this->x (для одного примера) относительно необычно. Когда код вообще хорошо написан, так же используется аксессор или мутатор.

хотя я довольно необычен в этом отношении, я пойду на запись (еще раз), сказав, что принуждение клиентского кода использовать аксессор или мутатор напрямую-плохая идея. Если у вас честно есть ситуация, когда имеет смысл для клиентского кода манипулируйте значением в объекте, затем клиентский код должен использовать обычное назначение для чтения и / или записи этого значения.

когда/Если вам нужно контролировать, какое значение назначено, перегрузка оператора позволяет вам взять этот контроль, не заставляя уродливый синтаксис get/set в клиентском коде. В частности, вам нужен прокси-класс (или шаблон класса). Например, одна из наиболее распространенных ситуаций, когда люди хотят получить/установить функции, - это что-то вроде числа, которое должно быть ограничен определенным диапазоном. The setXXX проверяет новое значение на наличие диапазона и getXXX возвращает значение.

если вы хотите этого, (довольно) простой шаблон может сделать работу намного более чисто:

template <class T, class less=std::less<T> >
class bounded {
    const T lower_, upper_;
    T val_;

    bool check(T const &value) {
        return less()(value, lower_) || less()(upper_, value);
    }

    void assign(T const &value) {
        if (check(value))
            throw std::domain_error("Out of Range");
        val_ = value;
    }

public:
    bounded(T const &lower, T const &upper) 
        : lower_(lower), upper_(upper) {}

    bounded(bounded const &init) 
        : lower_(init.lower), upper_(init.upper)
    { 
        assign(init); 
    }

    bounded &operator=(T const &v) { assign(v);  return *this; }

    operator T() const { return val_; }

    friend std::istream &operator>>(std::istream &is, bounded &b) {
        T temp;
        is >> temp;

        if (b.check(temp))
            is.setstate(std::ios::failbit);
        else
            b.val_ = temp;
        return is;
    }
};

Это также делает код намного ближе к самостоятельному документированию - например, когда вы объявляете объект как:bounded<int>(1, 1024);, сразу видно, что намерение является целым числом в диапазоне от 1 до 1024. Только кто-то может найти открытым для вопроса Является ли 1 и/или 1024 включен в диапазон. Это значительно отличается от определения int в классе и ожидает, что все, кто когда-либо смотрит на класс, поймут, что они должны использовать setXXX для обеспечения некоторого (в этот момент неизвестного) набора границ для значений, которые могут быть назначены.

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


как говорили другие, используйте указатели, если null является возможностью.

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

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


в чем разница между ними?

ссылка является псевдонимом вещи (it is вещь*). Указатель-это адрес, вещи. Если есть шанс, что того, на что указывают, не будет, то вы, вероятно, не захотите возвращать ссылки. Ссылки говорят вызывающему абоненту:"я собираюсь дать вам псевдоним, который будет существовать, когда я верну его вам". На самом деле нет никакого способа проверить ссылку, чтобы увидеть, действительно ли то, что лежит в основе.

С помощью указателя семантически вы подразумеваете, что вызывающий объект может захотеть проверить, существует ли элемент перед его использованием. Обычно это делается с помощью проверки NULL.

в конечном счете нет" правильного " ответа. Это зависит от контракта класса, и если вызывающий будет/должен/хочет проверить, все ли еще "член".

короткий ответ-это указатели на вещи, которые могут быть указаны в другом месте, и ссылки на "несмещенные" псевдонимы.


+1 на вопрос об использовании сеттеров и геттеров. Если вы должны использовать их и иметь возможность нулей, рассмотрите возможность использования boost::shared_ptr. Таким образом, владение обрабатывается для вас.


В дополнение к другим ответам, Если вы выбираете ссылки для геттера, не пишите так, как в вашем примере:

YourClass &Member(){
   return *this->pMember;
}

ваш геттер фактически позволяет устанавливать, как в instance->Member() = YourClass(); и, таким образом, в обход вашего сеттера. Это может быть запрещено, если ваш класс не является копируемым, но это еще одна вещь, которую нужно иметь в виду. Другим недостатком является то, что геттер не является const.

вместо этого напишите свой геттер следующим образом:

const YourClass &Member() const {
   return *this->pMember;
}

Джонатан, какой компилятор вы используете? Есть большой шанс, что shared_ptr уже поставляется с ним как часть реализации TR1 компилятора.