Я полагаю, что это вопрос лучшей практики.
В C ++ у меня есть класс-оболочка для пути к файловой системе, похожая на os.path в Python. В этом классе-обертке есть функция-член под названием «Split», которая ищет последний встречающийся разделитель пути и разбивает его на «хвост» (последнюю часть пути) и «голову» (все остальное). В настоящее время функция использует свою собственную переменную-член m_filepath для разделения.
Некоторый код:
class FilePath {
public:
void Split(FilePath& head, FilePath& tail) const
{
FilePath h;
FilePath t;
//initialize h, t with portions of m_filepath...
head = h;
tail = t;
}
private:
std::string m_filepath;
};
int main(int argc, char ** argv)
{
FilePath some_path_1("/");
FilePath some_path_2("/home/");
some_path_1.Split(some_path_1, some_path_2);
return 0;
}
Когда я делаю что-то вроде этого, m_filepath файла some_path_1 будет перезаписываться независимо от того, какой «головой» оказался. Оператор const тоже не против.
Мой вопрос, каков наилучший способ справиться с этим? Бросить исключение? Разрешить перезапись объекта (это меня беспокоит и звучит небезопасно) и попросить разработчиков быть осторожнее? Умно ли использовать оператор возврата?
Большая проблема с написанием кода таким способом — это соблазн очистить код так, чтобы разделение выглядело так:
void Split(FilePath & h, FilePath & t) {
h.m_filepath = getHead(m_filepath);
t.m_filepath = getTail(m_filepath);
}
Но с тех пор h.m_filepath = ...
на самом деле меняется this.m_filepath
второй звонок не делает то, что ожидается. (Обратите внимание, что ваш код в настоящее время в порядке, но хрупок).
Корень (потенциальной) проблемы — это сочетание использования ссылок в качестве возвращаемых значений и необходимости нескольких возвращаемых значений. Но C ++ 11 поддерживает множественные возвращаемые значения через tie
,
Так что я бы реализовать это как
class FilePath {
public:
std::pair<FilePath,FilePath> Split() const
{
FilePath h;
FilePath t;
//initialize h, t with portions of m_filepath...
head = h;
tail = t;
return std::make_pair(h,t);
}
private:
std::string m_filepath;
};
Тогда использование будет выглядеть так:
int main(int argc, char ** argv)
{
FilePath some_path_1("/");
FilePath some_path_2("/home/");
std::tie(some_path_1, some_path_2) = some_path_1.Split();
return 0;
}
И довольно ясно, что some_path_1
а также some_path_2
обновят их значения, и вам не нужно беспокоиться, что запись в аргументы изменит это, так как аргументов больше нет.
редактировать Ах! Я только что понял, что ты хотел Split()
делать: сплит this
в head
а также tail
— и надеюсь, что они не пройдут this
в любой head
ни tail
,
В другом сценарии operator =(Class &rhs)
Лучше всего сначала сравнить &rhs
против this
чтобы избежать этой точной проблемы — но я не думаю, что у вас есть эта проблема в любом случае. При использовании, которое вы дали, не только ясно, что произойдет, ваша реализация достаточно хороша, чтобы позволить этому случиться. Я бы не волновался (как сказал @MM).
Когда у вас есть функция-член, ожидается, что функция будет работать с экземпляром класса: Split()
или выключи какую-то информацию о себе.
То, что вы хотите, это static
функция-член: функция, которая, хотя и связана с ее классом, не работает с предполагаемым this
, Сделать Split()
функция static
:
static void Split(...); // Note no concept of const required - no `this` to modify!
и назовите это так:
FilePath::Split(some_path_1, some_path_2);
Или вы можете сделать так, чтобы Split()
возвращает хвост и изменяет себя, чтобы быть просто головой:
FilePath Split();
Но в этом случае немного неясно, в каком направлении происходит раскол. Как насчет:
FilePath SplitHead();
FilePath SplitTail();
Эти два последних могут быть реализованы с точки зрения static
Метод выше, вот так:
FilePath FilePath::SplitHead() {
FilePath head = *this;
Split(head, *this);
return head;
} // FilePath::SplitHead()
FilePath FilePath::SplitTail() {
FilePath tail;
Split(*this, tail);
return tail;
} // FilePath::SplitTail()