Мое разделение класса между заголовком/источником, кажется, работает, пока я не попытаюсь работать с объявлением класса в пространстве имен. Я считаю, что это связано с областью действия оператора в указанном пространстве имен, но я не знаю синтаксиса для правильной перегрузки оператора таким образом.
NameTest.h
#pragma once
#include <iostream>
namespace testspace
{
class Test
{
private:
int number{};
public:
friend std::ostream& operator<< (std::ostream& out, const Test& test);
};
}
NameTest.cpp
#include "NameTest.h"
using namespace testspace;
std::ostream& operator<< (std::ostream& out, const Test& test)
{
out << test.number;
return out;
}
Я знаю, что это разрешается так в заголовке...
(std::ostream &testspace::operator<< (std::ostream &out, const testspace::Test& test);
... и я думаю, что это как-то связано с разрешением области, но я не знаю, проблема ли это в том, как я реализую пространство имен, оператор или сам класс. По крайней мере, я знаю, что когда я удаляю объявление класса из пространства имен, все работает нормально. Это связано с разрешением области в .cpp? И как бы я это реализовал?
Заранее спасибо.
Обновлено: test.number недоступен в определении перегрузки оператора в его нынешнем виде...
namespace testspace { std::ostream& operator<< (std::ostream& out, const Test& test) { ... } }
в исходном файле - так же, как в заголовочном файле.
@heap underrun - так что у меня возникнут проблемы с 'namespace testspace {' как в заголовке, так и в источнике? Или он просто добавляет то, что существует в пространстве имен?
Вы можете открыть пространство имен с помощью namespace foo {
, чтобы добавлять в него элементы столько раз, сколько вам нужно в разных файлах.
Это помогает Джесперу, спасибо!
Когда вы говорите using namespace testspace;
в исходном файле, это просто импортирует имена из пространства имен для поиска имен. Если вы хотите добавить что-то в пространство имен, например, ваш operator<<
, вам нужно открыть пространство имен с помощью
namespace testspace {
а затем добавьте реализацию оператора, а затем снова закройте пространство имен
}
Вы можете открывать пространство имен для дополнений столько раз, сколько вам нужно/хотите, в нескольких файлах или внутри одного и того же файла. Это все добавки.
Есть тонкая причина, по которой определение operator<<
должно быть сделано внутри пространства имен testspace
. Это связано с особым статусом, который присваивается operator
функциям, объявленным в классе. Обычно на члены класса нужно ссылаться с помощью dot operator
. Однако мы обычно не делаем этого с операторами. Вместо этого мы просто используем их.
Это работает, потому что операторы, объявленные внутри класса, автоматически становятся видимыми в области, в которой объявлен класс. Это место, куда смотрит компоновщик, когда ему нужно найти определение для вашего оператора-друга.
Вы сказали компоновщику, что ваш оператор определен в пространстве имен testspace
, но оператор, который вы определяете в файле NameTest.cpp
, находится на «уровне файла».
Исправление предоставлено @Jesper Juhl. Переместите определение в пространство имен testspace
.
// NameTest.cpp
#include "NameTest.h"
using namespace testspace;
namespace testspace
{
std::ostream& operator<< (std::ostream& out, const Test& test)
{
out << test.number;
return out;
}
}
Вот небольшое излишество, которое доказывает эту точку. Это то, что вы бы сделали, если бы решили сохранить определение operater<<
на «уровне файлов».
// NameTest.h
#pragma once
#include <iostream>
namespace testspace
{
// Forward declaration tells everyone that class Test
// resides in namespace testspace.
class Test;
}
// The operator declared here is declared at the "file-level".
// Its right operand, however, will be found in namespace testspace.
std::ostream& operator<< (std::ostream& out, const testspace::Test& test);
namespace testspace
{
class Test
{
private:
int number{};
public:
// The friend declared here is an operator that has "file-level" scope.
// Note the :: right before the keyword operator.
friend std::ostream& ::operator<< (std::ostream& out, const Test& test);
};
}
// NameTest.cpp
#include "NameTest.h"
using namespace testspace;
// This operator is defined at the "file level."
// The right operand must be scoped to namespace testspace.
std::ostream& operator<< (std::ostream& out, const testspace::Test& test)
{
out << test.number;
return out;
}
Хотите верьте, хотите нет, но эта программа работает, как и другие, приведенные в этом ответе. Я протестировал их со следующим драйвером, который выводит t : 0
.
// main.cpp
#include <iostream>
#include "NameTest.h"
int main()
{
testspace::Test t;
std::cout << "t : " << t << "\n\n";
return 0;
}
Однако в целом мне не нравится ни один из приведенных выше ответов. Вместо этого я бы использовал идиому «скрытые друзья». Этот метод просто перемещает определение operator<<
в сам класс.
Вы можете узнать больше о скрытых друзьях в моем ответе на этот вопрос StackOverflow.
Я часто inline
оператор, но это не обязательно.
// NameTest.h
#pragma once
#include <iostream>
namespace testspace
{
class Test
{
private:
int number{};
private:
inline friend auto operator<< (std::ostream& out, const Test& test)
-> std::ostream&
{
out << test.number;
return out;
}
};
}
Во избежание путаницы я обычно объявляю своих скрытых друзей как public
. Однако в этом примере я сделал это private
. На самом деле access specifier
может быть public
, private
или protected
. Неважно какой. Дружба не имеет ничего общего со спецификаторами доступа.
Определяемый скрытый друг не является членом класса. Это оператор, видимый в области видимости за пределами класса и фактически находящийся там. Для функции, которая не является членом класса и находится вне класса, нет смысла говорить о public
, private
и protected
.
Кстати, если вы используете идиому скрытого друга, не забудьте удалить определение operator<<
из файла NameTest.cpp
.
Боковые примечания:
ts
для testspace
.namespace ts = testspace;
using namespace testspace
и using namespace std
. Просто запишите нужные имена с полной областью действия. Например, учитывая псевдоним пространства имен ts
, легко написать ts::Test
и так далее.
using namespace X;
просто добавляет пространство имен для целей поиска. Чтобы определить что-то в пространстве имен, вам нужноnamespace X { ... }