Оператор перегрузки << - C ++ - PullRequest
5 голосов
/ 02 марта 2009

Фон

У меня есть контейнерный класс, который внутренне использует вектор . Я предоставил метод AddChar (std :: string) для этого класса-оболочки, который выполняет push_back () для внутреннего вектора. В моем коде я должен добавить несколько элементов в контейнер некоторое время. Для этого я должен использовать

container.AddChar("First");
container.AddChar("Second");

Это делает код больше. Поэтому, чтобы сделать это проще, я планирую перегрузить оператор <<. Так что я могу написать </p>

container << "First" << "Second"

и два элемента будут добавлены к базовому вектору.

Вот код, который я использовал для этого

class ExtendedVector
{
private:
    vector<string> container;

public:
    friend ExtendedVector& operator<<(ExtendedVector& cont,const std::string str){
        cont.AddChar(str);
        return cont;
    }

    void AddChar(const std::string str)
    {
        container.push_back(str);
    }

    string ToString()
    {
        string output;
        vector<string>::iterator it = container.begin();
        while(it != container.end())
        {
            output += *it;
            ++it;
        }
        return output;
    }
};

Работает как положено.

Вопросы

  1. Правильно ли записана перегрузка оператора?
  2. Является ли хорошей практикой перегрузка операторов в подобных ситуациях?
  3. Будут ли проблемы с производительностью или другие проблемы с этим кодом?

Есть мысли?

Редактировать

Услышав отличные комментарии, я решил не перегружать <<, поскольку здесь это не имеет смысла. Я удалил код перегрузки оператора, и вот окончательный код. </p>

class ExtendedVector
{
private:
    vector<string> container;

public:

    ExtendedVector& AddChar(const std::string str)
    {
        container.push_back(str);
        return *this;
    }

         .. other methods
}

Это позволяет мне добавить

container.AddChar("First").AddChar("Second")

В C # я могу сделать это проще, используя ключевое слово params. Код будет как

void AddChar(params string[] str)
{
    foreach(string s in str)
       // add to the underlying collection
}

Я знаю, что в C ++ мы можем использовать ... для указания переменной длины параметров. Но AFAIK, это не тип безопасности. Так это рекомендуемая практика? Так что я могу написать

container.AddChar("First","Second")

Спасибо за ответы.

Ответы [ 7 ]

8 голосов
/ 02 марта 2009

Правильно ли записана перегрузка оператора?

Да, но можно добиться большего. Как кто-то еще упомянул, ваша функция может быть полностью определена из существующих, открытых функций. Почему бы не использовать его только те? Прямо сейчас это друг, а это значит, что он относится к деталям реализации. То же самое верно, если вы поместите operator << в качестве члена в ваш класс. Тем не менее, сделайте так, чтобы ваш оператор << a <em>не являлся членом , не являлся другом функция.

class ExtendedVector {
    ...
};

// note, now it is *entirely decoupled* from any private members!
ExtendedVector& operator<<(ExtendedVector& cont, const std::string& str){
    cont.AddChar(str);
    return cont;
}

Если вы измените свой класс, вы не будете уверены, что ваш оператор << все еще будет работать. Но если ваш оператор << полностью зависит только от открытых функций, то вы можете быть уверены, что он будет работать после того, как были внесены изменения только в детали реализации вашего класса. Ура! </p>

Является ли хорошей практикой перегрузка операторов в подобных ситуациях?

Как снова сказал другой парень, это спорно. Во многих ситуациях перегрузка операторов на первый взгляд будет выглядеть «аккуратно», но в следующем году будет выглядеть адом, потому что вы больше не имеете ни малейшего представления о том, что вы имели в виду, когда наделяли некоторые символы особой любовью. В случае оператора <<, я думаю, что это нормально использовать. Его использование в качестве оператора вставки для потоков хорошо известно. И я знаю о приложениях Qt и KDE, которые широко используют его в таких случаях, как </p>

QStringList items; 
items << "item1" << "item2";

Аналогичным случаем является boost.format, который также использует operator% для передачи аргументов для заполнителей в своей строке:

format("hello %1%, i'm %2% y'old") % "benny" % 21

Конечно, также можно использовать его там. Но его использование для спецификаций формата printf хорошо известно, и поэтому его использование тоже хорошо, imho. Но, как всегда, стиль тоже субъективен, поэтому возьмите его с крошкой соли:)

Как я могу принимать аргументы переменной длины безопасным способом?

Ну, есть способ принять вектор, если вы ищете однородные аргументы:

void AddChars(std::vector<std::string> const& v) {
    std::vector<std::string>::const_iterator cit =
        v.begin();
    for(;cit != v.begin(); ++cit) {
        AddChar(*cit);
    }
}

Это не очень удобно, чтобы пройти это все же. Вы должны создать свой вектор вручную, а затем передать ... Я вижу, у вас уже есть правильное представление о функциях стиля vararg. Не следует использовать их для такого рода кода и только при взаимодействии с кодом C или функциями отладки, если вообще. Другой способ справиться с этим случаем - применить программирование препроцессора. Это сложная тема и довольно хакерская. Идея состоит в том, чтобы автоматически генерировать перегрузки до некоторого верхнего предела, примерно так:

#define GEN_OVERLOAD(X) \
void AddChars(GEN_ARGS(X, std::string arg)) { \
    /* now access arg0 ... arg(X-1) */ \
    /* AddChar(arg0); ... AddChar(arg(N-1)); */ \
    GEN_PRINT_ARG1(X, AddChar, arg) \
}

/* call macro with 0, 1, ..., 9 as argument
GEN_PRINT(10, GEN_OVERLOAD)

Это псевдокод. Вы можете посмотреть библиотеку препроцессора boost здесь .

Следующая версия C ++ предложит гораздо лучшие возможности. Можно использовать списки инициализаторов:

void AddChars(initializer_list<std::string> ilist) {
    // range based for loop
    for(std::string const& s : ilist) {
        AddChar(s);
    }
}

...
AddChars({"hello", "you", "this is fun"});

В следующем C ++ также возможно поддерживать произвольное множество аргументов (смешанного типа) с использованием шаблонов с переменными числами . GCC4.4 будет поддерживать их. GCC 4.3 уже частично их поддерживает.

3 голосов
/ 02 марта 2009

1) Да, за исключением того, что AddChar является общедоступным, нет причин, по которым он должен быть friend.

2) Это спорно. << вроде как оператор, чья перегрузка для "странных" вещей, по крайней мере, неохотно принимается.

3) Ничего очевидного. Как всегда, профилирование - твой друг. Вы можете рассмотреть возможность передачи строковых параметров в AddChar и operator<< по константной ссылке (const std::string&), чтобы избежать ненужного копирования.

3 голосов
/ 02 марта 2009

Это хорошая практика для перегрузки операторы в подобных ситуациях?

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

2 голосов
/ 02 марта 2009

Перегрузка операторов в этом случае не является хорошей практикой, поскольку делает код менее читабельным. Стандарт std::vector не имеет этого также для толкания элементов, по уважительным причинам.

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

container.AddChar("First").AddChar("Second");

Это будет возможно, если у вас есть AddChar() return *this.

Забавно, что у вас есть эта toString() функция. В случае в случае стандартная вещь для использования в этом случае - operator<<! Так что если вы хотите использовать операторы, сделайте функцию toString() operator<<.

2 голосов
/ 02 марта 2009

Я бы предпочел не перегружать его таким образом лично, потому что векторы обычно не имеют перегруженного оператора сдвига влево - это не совсем идиома; -)

Я бы, вероятно, вместо этого вернул ссылку из AddChar:

ExtendedVector& AddChar(const std::string& str) {
    container.push_back(str);
    return *this;
}

так что вы можете сделать

container.AddChar("First").AddChar("Second");

, который на самом деле не намного больше, чем операторы битового сдвига.

(также см. Комментарий Логана о передаче строк по ссылке, а не по значению).

1 голос
/ 02 марта 2009

Оператор здесь неправильно перегружен. Нет причин делать оператора другом, так как он может быть членом класса. Friend - для функций, которые не являются действительными членами класса (например, при перегрузке << для ostream, чтобы объект мог быть выведен в cout или ofstreams). </p>

То, что вы действительно хотите, чтобы оператор был:

ExtendedVector& operator<<(const std::string str){
    AddChar(str);
    return *this;
}

Обычно считается плохой практикой перегружать операторы так, чтобы они делали что-то, чем они обычно делают. << обычно является битовым сдвигом, поэтому его перегрузка может привести к путанице. Очевидно, STL перегружает << для «вставки потока», и поэтому наряду с тем, что <em>может имеет смысл перегрузить его для вашего использования подобным образом. Но это не похоже на то, что вы делаете, поэтому вы, вероятно, хотите этого избежать.

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

0 голосов
/ 02 марта 2009

Это может запутать, я бы использовал тот же синтаксис, что и std :: cin, в переменную:

std::cin >> someint;

"First" >> container;

Таким образом, это как минимум оператор вставки. Для меня, когда что-то имеет << перегруженный оператор, я ожидаю, что оно будет что-то выводить. Так же, как std :: cout. </p>

...