Вопрос

Фон

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

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

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

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. Будут ли какие-либо проблемы с производительностью или другие проблемы с этим кодом?

Есть предположения?

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

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

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")

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

Это было полезно?

Решение

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

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

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;
}

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

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

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

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 уже частично их поддерживает.

Другие советы

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

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

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

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

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

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

Вместо этого я бы, вероятно, вернул ссылку из AddChar следующим образом:

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

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

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

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

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

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

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

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

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

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

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

Каким на самом деле должен быть оператор:

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

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

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

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

std::cin >> someint;

"First" >> container;

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top