Я изучаю итераторы С++. У меня была странная ошибка при попытке получить элемент структуры из итератора, я получал неправильный номер. После долгих проб и ошибок я обнаружил, что это printf печатает неправильное число при подаче члена структуры.
Этот код дает правильный результат:
int n_payload = cl_custom_iterator.get_payload();
std::cout << "get_payload: " << n_payload << "\n";
Этот код дает неверный результат:
int n_payload = cl_custom_iterator.get_payload();
My_class::Node st_node = cl_custom_iterator.get_node();
printf("%p | get_payload: %d | get_node.n_payload: %d\n", cl_custom_iterator, n_payload, st_node.n_payload);
Это минимальный пример, который воспроизводит проблему:
#include <iostream>
#include <cstdio>
#include <vector>
class My_class
{
public:
//Troublemaker?
struct Node
{
int n_payload;
};
//Constructor
My_class(void)
{
Node st_node;
for (int cnt = 0; cnt < 5; cnt++)
{
st_node.n_payload = 33+cnt;
gast_my_array.push_back( st_node );
std::cout << "Push " << cnt << " | Payload: " << gast_my_array[cnt].n_payload << "\n";
}
}
template <typename T>
class iterator
{
public:
//! @brief constructor for the custom iterator
iterator(std::vector<T>& ira_parent_vector, size_t in_starting_index = 0) :
gra_vector(ira_parent_vector),
gu32_index(in_starting_index)
{
//Do nothing
return;
}
iterator<T>& operator++()
{
gu32_index++;
return *this;
}
iterator<T> operator++(int)
{
iterator<T> tmp(*this);
gu32_index++;
return tmp;
}
T &operator &(void)
{
return &gra_vector[gu32_index];
}
int get_payload( void )
{
return gra_vector[gu32_index].n_payload;
}
T get_node( void )
{
return gra_vector[gu32_index];
}
bool operator==(const iterator<T>& icl_rhs_iterator) const
{
return gu32_index == icl_rhs_iterator.gu32_index;
}
bool operator!=(const iterator<T>& icl_rhs_iterator) const
{
return gu32_index != icl_rhs_iterator.gu32_index;
}
private:
std::vector<T>& gra_vector;
size_t gu32_index;
};
iterator<Node> begin()
{
return iterator<Node>(gast_my_array, 0);
}
iterator<Node> end()
{
return iterator<Node>(gast_my_array, gast_my_array.size());
}
private:
std::vector<Node> gast_my_array;
};
int main(void)
{
My_class my_class_instance;
for (My_class::iterator<My_class::Node> cl_custom_iterator=my_class_instance.begin();cl_custom_iterator!=my_class_instance.end();cl_custom_iterator++)
{
int n_payload = cl_custom_iterator.get_payload();
My_class::Node st_node = cl_custom_iterator.get_node();
//std::cout << "get_payload: " << cl_custom_iterator.get_payload();
std::cout << "get_payload: " << n_payload << "\n";
printf("%p | get_payload: %d | get_node.n_payload: %d\n", cl_custom_iterator, n_payload, st_node.n_payload);
}
return 0;
}
Это вывод, который я получаю на своей машине, скомпилированной с помощью mingw -std=c++11.
get_payload, переданный в std::cout, и ручное извлечение члена структуры из узла дают правильный результат 33, 34,...
get_payload, переданный в printf, выводит неправильное число 1, 2, ...
Push 1 | Payload: 34
Push 2 | Payload: 35
Push 3 | Payload: 36
Push 4 | Payload: 37
get_payload: 33
006efec8 | get_payload: 0 | get_node.n_payload: 33
get_payload: 34
006efec8 | get_payload: 1 | get_node.n_payload: 34
get_payload: 35
006efec8 | get_payload: 2 | get_node.n_payload: 35
get_payload: 36
006efec8 | get_payload: 3 | get_node.n_payload: 36
get_payload: 37
006efec8 | get_payload: 4 | get_node.n_payload: 37
Process returned 0 (0x0) execution time : 0.141 s
Press any key to continue.
Я не могу понять, что я делаю неправильно.
Фиксированный код
#include <iostream>
#include <vector>
class My_class
{
public:
struct Node
{
int n_payload;
};
//Constructor with example
My_class()
{
Node st_node;
for (int cnt = 0; cnt < 5; cnt++)
{
st_node.n_payload = 33+cnt;
gast_my_array.push_back( st_node );
std::cout << "Push " << cnt << " | Payload: " << gast_my_array[cnt].n_payload << "\n";
}
}
//iterator
template <typename T>
class iterator
{
public:
//! @brief constructor for the custom iterator
iterator(std::vector<T>& ira_parent_vector, size_t in_starting_index = 0) :
gra_vector(ira_parent_vector),
gu32_index(in_starting_index)
{
//Do nothing
return;
}
iterator<T>& operator++()
{
gu32_index++;
return *this;
}
iterator<T> operator++(int)
{
iterator<T> tmp(*this);
gu32_index++;
return tmp;
}
//FIX: I overload the * operator to get a reference to the element of std::vector of which I can easily get the address and content
T &operator *()
{
return gra_vector[gu32_index];
}
bool operator==(const iterator<T>& icl_rhs_iterator) const
{
return gu32_index == icl_rhs_iterator.gu32_index;
}
bool operator!=(const iterator<T>& icl_rhs_iterator) const
{
return gu32_index != icl_rhs_iterator.gu32_index;
}
private:
std::vector<T>& gra_vector;
size_t gu32_index;
};
iterator<Node> begin()
{
return iterator<Node>(gast_my_array, 0);
}
iterator<Node> end()
{
return iterator<Node>(gast_my_array, gast_my_array.size());
}
private:
std::vector<Node> gast_my_array;
};
int main(void)
{
My_class my_class_instance;
for (My_class::iterator<My_class::Node> cl_custom_iterator=my_class_instance.begin();cl_custom_iterator!=my_class_instance.end();cl_custom_iterator++)
{
//FIX: printf has undefined behaviour with %p of an object. std::cout of the address of a reference will print out the address
std::cout << "iterator address: " << &*cl_custom_iterator << " | payload: " << (*cl_custom_iterator).n_payload << "\n";
}
return 0;
}
Выход (правильный):
Push 0 | Payload: 33
Push 1 | Payload: 34
Push 2 | Payload: 35
Push 3 | Payload: 36
Push 4 | Payload: 37
iterator address: 0x711720 | payload: 33
iterator address: 0x711724 | payload: 34
iterator address: 0x711728 | payload: 35
iterator address: 0x71172c | payload: 36
iterator address: 0x711730 | payload: 37
Process returned 0 (0x0) execution time : 0.109 s
Press any key to continue.
Компилятор выдает мне предупреждение при использовании %p для объекта класса, но компилирует и связывает все, что угодно. Я удалил %p, и теперь printf выводит правильное число, вы правы. Я использую printf %p, потому что считаю это быстрым способом получить адреса и узнать, что происходит под капотом, поверхностные копии, печатные копии, ссылки и еще много чего. Это первый раз, когда я увидел, что %p вызывает сбой в работе printf.
Пожалуйста, послушайте компилятор, он знает, о чем говорит. Чтобы напечатать адрес чего-либо с помощью printf
и %p
, вы, прежде всего, должны убедиться, что у вас есть реальный указатель, а затем убедиться, что это указатель void*
. Несоответствие спецификатора формата и типа аргумента приводит к UB. Или, что еще лучше, вообще не используйте небезопасную по типу функцию printf
в коде C++.
Я слышу вас, я изучаю C++ и пытаюсь избавиться от вредных привычек, которые у меня были с C. У вас есть какие-нибудь предложения о том, как распечатать адрес объекта?
Предполагая, что вы хотите напечатать адрес объекта, на который «указывает» итератор, затем разыменуйте итератор, чтобы получить ссылку на объект, а затем используйте оператор указателя на &
, чтобы получить указатель на объект. Нравится &*cl_custom_iterator
. И вы можете использовать его как есть с std::cout
как std::cout << &*cl_custom_iterator << " | get_payload: " << n_payload << " | get_node.n_payload: " << st_node.n_payload << '\n';
Компилятор запутался error: no match for 'operator*' (operand type is 'My_class::iterator<My_class::Node>')|
мой итератор перегружает оператор * для получения содержимого (структуры Node) при сканировании итератора. Я мог бы создать специальный метод, чтобы получить строку с адресом внутри из this
внутри класса, но это похоже на шаблон.
О, в вашем итераторе нет перегруженного оператора *
, как это принято. Или перегруженный оператор ->
. Вместо этого у него есть перегруженный оператор указателя T &operator &(void)
, который возвращает указатель (правильно), но тип возврата неверен. Должно быть T*
. Если вы исправите тип возвращаемого значения, вы можете использовать &cl_custom_iterator
, чтобы получить указатель на объект Node
. Однако перегрузка этого оператора указателя на очень редка.
operator &
должно быть operator *
, а return &gra_vector[gu32_index];
должно быть return gra_vector[gu32_index];
@molbdnilo это единственная работающая комбинация! T &operator *(void) { return gra_vector[gu32_index]; } std::cout << "iterator address: " << &*cl_custom_iterator << "\n"; iterator address: 0x10a1720
Все остальные комбинации, даже убрав перегрузку, компилятор путается и не может разрешить адрес как текст.
Да, это ожидаемо. (Отбросьте привычку параметра (void)
и напишите ()
. Они эквивалентны в C++.)
@molbdnilo, если вы опубликуете это как ответ, я могу это принять. Спасибо всем за помощь.
Нет, компилятор не "счастлив". Это дает вам предупреждение. Рассматривайте все предупреждения как ошибки.
Я думаю, что это дурацкая затея не иметь никаких предупреждений. Разные компиляторы будут сбиты с толку/недовольны разными вещами. Я помню код, который не выдавал предупреждений на mingw, но даже не компилировался компиляторами Visual Studio. Я использую GCC, потому что он имеет более стабильное поведение при нацеливании на X64 в Windows и X64 в Linux.
Смотри, ты получил предупреждение, которое ты не понял, не смог обработать как ошибку и был укушен им. На вашем месте я бы воздержался от того, чтобы говорить людям, что они дураки из-за политики отсутствия предупреждений хотя бы пару недель.
Функции C, такие как printf
, ничего не знают о типах C++, таких как итераторы, и это компилируется только потому, что printf
является функцией с переменным числом аргументов, и поэтому нет проверки типов ее аргументов.
Вы можете попробовать это вместо этого
printf("%p | get_payload: %d | get_node.n_payload: %d\n",
&*cl_custom_iterator, n_payload, st_node.n_payload);
использование &*cl_custom_iterator
должно гарантировать, что аргумент является указателем, как того требует спецификатор формата %p
.
Хотя, глядя на ваш класс iterator
, я не уверен, что он скомпилируется.
Я получаю: error: no match for 'operator*' (operand type is 'My_class::iterator<My_class::Node>')|
Мне всегда неудобно пытаться получить печатный адрес для объекта, поэтому по умолчанию я использую %p. Вы знаете, как это сделать? Меня не волнует printf, я доволен использованием std::cout.
@05032MendicantBias Да, я так и подозревал. Ваш итератор должен определять operator*
(который возвращает ссылку на элемент, на который ссылается итератор). Без operator*
класс you не является настоящим итератором. Он также должен определить operator->
, который возвращает указатель на элемент, на который ссылаются.
@ 05032MendicantBias После того, как вы определили operator*
и вернули ссылку, трюк &*
сработает.
@ 05032MendicantBias Я предполагаю, что вам нужен адрес элемента, на который ссылается итератор, если вам нужен адрес самого итератора, то это просто &cl_custom_iterator
. А вот текущую operator&
пришлось бы убрать, что очень странно. Редко можно определить operator&
. Возможно, вам нужно пересмотреть работу итераторов.
Код с
printf
действительно строится?cl_custom_iterator
— это не указатель, это объект C++, о котором старая функция Cprintf
ничего не знает. Попытка напечатать значение объектов в формате%p
приводит к неопределенному поведению.