Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > C/C++ программирование > Общие вопросы C/C++
Регистрация

Восстановить пароль
Повторная активизация e-mail

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 17.12.2013, 16:27   #1
220Volt
Форумчанин
 
Регистрация: 14.12.2012
Сообщений: 668
По умолчанию Проблема при понижении типа указателя.

Доброго дня.
Сегодня осознал одну проблему, не то чтобы не знал о ней, а скорее раньше не задумывался. Например у нас есть код:
Код:
#include <iostream>

using namespace std;


struct S                
{ 
    int t; 
};

struct S2 : public S    
{ 
    int q;
};

void f(const S *const ps, int size)
{
    for(int i = 0;  i < size;  ++ i)
        cout << ps[i].t << "\n";
}

int main()
{
    constexpr int size = 5;
    S2 ar[size];
    
    for(int i = 0;  i < size;  ++ i)
        ar[i].t = ar[i].q = i;
    
    f(ar, size);
    
    int wait; cin >> wait;
    return 0;
}
После его выполнения мы обнаружим что он работает не так как мы ожидаем, вместо "cout 0 1 2 3 4" мы получим "cout 0 0 1 1 2". Немного поразмыслив набросал это:
Код:
#include <iostream>
#include <typeinfo>
#include <cassert>

using namespace std;


struct S                
{ 
    int t; 
    virtual ~S() {}
};

struct S2 : public S    
{ 
    int q;
};

void f(const S *const ps, int size)
{
    assert( typeid(S) == typeid(*ps) );
    
    for(int i = 0;  i < size;  ++ i)
        cout << ps[i].t << "\n";
}

int main()
{
    constexpr int size = 5;
    S2 ar[size];
    
    for(int i = 0;  i < size;  ++ i)
        ar[i].t = ar[i].q = i;
    
    f(ar, size);
    
    int wait; cin >> wait;
    return 0;
}
Это решает проблему, но меня не покидает чувство что как-то это криво. К тому же мы можем забыть написать виртуальную функцию в S. Ведь нужна всего лишь возможность запретить компилятору приводить тип указателя к базовому типу при вызове void f(const S *const ps, int size), например:
Код:
void f(const S *nocast const ps, int size)
{
    for(int i = 0;  i < size;  ++ i)
        cout << ps[i].t << "\n";
}
Вопрос: может есть в языке такие возможности, а я просто не знаю?
P.s: изобилие const при написании функции f() для демонстрации того что это не поможет.
PPS: под возможностью запретить компилятору приводить тип указателя к базовому типу подразумевал выдачу ошибки при попытки это сделать. И говорил о выборочном запрете каста, не везде.

Последний раз редактировалось 220Volt; 17.12.2013 в 20:20. Причина: Добавил PPS
220Volt вне форума Ответить с цитированием
Старый 17.12.2013, 16:36   #2
Пепел Феникса
Старожил
 
Аватар для Пепел Феникса
 
Регистрация: 28.01.2009
Сообщений: 21,000
По умолчанию

а кто запрещает применить не public наследование?
Хорошо поставленный вопрос это уже половина ответа. | Каков вопрос, таков ответ.
Программа делает то что написал программист, а не то что он хотел.
Функции/утилиты ждут в параметрах то что им надо, а не то что вы хотите.
Пепел Феникса вне форума Ответить с цитированием
Старый 17.12.2013, 16:38   #3
220Volt
Форумчанин
 
Регистрация: 14.12.2012
Сообщений: 668
По умолчанию

Разве это поможет? Размер каждого наследника будет суммой предков, в этом проблема. Или я Вас не понял, тогда раскройте мысль пожалуйста.
220Volt вне форума Ответить с цитированием
Старый 17.12.2013, 16:42   #4
Пепел Феникса
Старожил
 
Аватар для Пепел Феникса
 
Регистрация: 28.01.2009
Сообщений: 21,000
По умолчанию

Цитата:
Сообщение от 220Volt Посмотреть сообщение
Разве это поможет? Размер каждого наследника будет суммой предков, в этом проблема. Или я Вас не понял, тогда раскройте мысль пожалуйста.
не будет суммой предков, а согласно наследования лишь то что добавлено, не более.

да и в чем вообще размер? если вам не нужно наследование, то зачем применили?

выражайтесь яснее.
Хорошо поставленный вопрос это уже половина ответа. | Каков вопрос, таков ответ.
Программа делает то что написал программист, а не то что он хотел.
Функции/утилиты ждут в параметрах то что им надо, а не то что вы хотите.
Пепел Феникса вне форума Ответить с цитированием
Старый 17.12.2013, 16:50   #5
220Volt
Форумчанин
 
Регистрация: 14.12.2012
Сообщений: 668
По умолчанию

Я говорю о том что нельзя сделать массив из наследников, а потом работать с этим массивом к с массивом из предков. Но мы можем предвидеть такие ситуации (если мы в функции применяем адресную арифметику), и хотелось бы сказать об этом компилятору, а он не позволит понижать тип указателя и тогда проблемы не будет.
220Volt вне форума Ответить с цитированием
Старый 17.12.2013, 16:51   #6
220Volt
Форумчанин
 
Регистрация: 14.12.2012
Сообщений: 668
По умолчанию

Цитата:
Сообщение от Пепел Феникса Посмотреть сообщение
не будет суммой предков, а согласно наследования лишь то что добавлено, не более.

....
А как же множественное наследование? Но это не важно, я не об этом.

Последний раз редактировалось 220Volt; 17.12.2013 в 16:53.
220Volt вне форума Ответить с цитированием
Старый 17.12.2013, 17:07   #7
waleri
Старожил
 
Регистрация: 13.07.2012
Сообщений: 6,331
По умолчанию

Без добавления хотя бы одного виртуального метода typeid НЕ работает.
Насчет "приведения" к родительскому классу - если напечатать сам изначальный указатель на S2 а потом полученный указатель на S то увидите, что они одинаковы.
Если запретить такой каст, то от С++ ничего не останется - на этом все держится.

Какова конечная цель?
waleri вне форума Ответить с цитированием
Старый 17.12.2013, 17:08   #8
pproger
C++ hater
СтарожилДжуниор
 
Аватар для pproger
 
Регистрация: 19.07.2009
Сообщений: 3,333
По умолчанию

2220Volt
лень объяснять, напишу в двух словах. добавив виртуальный деструктор, ты не решил своей проблемы (добавь в дочерний класс еще пару полей и убедись). правильное решение - добавить виртуальную функцию, получать доступ к данным через нее. либо можно сделать так:
Код:
#include <iostream>
#include <stdint.h>

using namespace std;

struct S                
{ 
	int t; 
};

struct S2 : public S    
{ 
	int q;
};

void f(S **ps, int size)
{
	for(int i = 0;  i < size;  ++ i)
		cout << ps[i]->t << "\n";
}

int main()
{
	const int size = 5;
	S2 ar[size];

	S *arr[size];

	for(int i = 0;  i < size;  ++ i) {
		ar[i].t = ar[i].q = i;
		arr[i] = &ar[i];
	}

	f(arr, size);

	return 0;
}
почитай c++ gotchas, там был этот момент описан
I invented the term Object-Oriented, and I can tell you I did not have C++ in mind. (c)Alan Kay

My other car is cdr.

Q: Whats the object-oriented way to become wealthy?
A: Inheritance
pproger вне форума Ответить с цитированием
Старый 17.12.2013, 17:23   #9
220Volt
Форумчанин
 
Регистрация: 14.12.2012
Сообщений: 668
По умолчанию

Без добавления хотя бы одного виртуального метода typeid НЕ работает.

У меня на g++ без виртуального метода компилируется и работает, но typid работает неверно, следовательно полагаться на то что компилятор выдаст ошибку не хочу.

Насчет "приведения" к родительскому классу - если напечатать сам изначальный указатель на S2 а потом полученный указатель на S то увидите, что они одинаковы.
...
Я и не говорю что они будут разные, я говорю что, например, инкремент указателя нужно производить по разному для предка и для наследника.
220Volt вне форума Ответить с цитированием
Старый 17.12.2013, 17:26   #10
220Volt
Форумчанин
 
Регистрация: 14.12.2012
Сообщений: 668
По умолчанию

Цитата:
Сообщение от pproger Посмотреть сообщение
2220Volt
лень объяснять, напишу в двух словах. добавив виртуальный деструктор, ты не решил своей проблемы (добавь в дочерний класс еще пару полей и убедись). правильное решение - добавить виртуальную функцию, получать доступ к данным через нее. либо можно сделать так:
Код:
#include <iostream>
#include <stdint.h>

using namespace std;

struct S                
{ 
	int t; 
};

struct S2 : public S    
{ 
	int q;
};

void f(S **ps, int size)
{
	for(int i = 0;  i < size;  ++ i)
		cout << ps[i]->t << "\n";
}

int main()
{
	const int size = 5;
	S2 ar[size];

	S *arr[size];

	for(int i = 0;  i < size;  ++ i) {
		ar[i].t = ar[i].q = i;
		arr[i] = &ar[i];
	}

	f(arr, size);

	return 0;
}
почитай c++ gotchas, там был этот момент описан
Идею понял, но хотелось бы другого решения. За ссылку спасибо, почитаю.

Последний раз редактировалось 220Volt; 17.12.2013 в 17:29.
220Volt вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц



Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
C#, проблема с получением указателя через Marshal.GetFunctionPointerForDelega te Alex217Vish Общие вопросы .NET 0 15.09.2010 23:23
мигание TLabel при наведении указателя skalt12 Общие вопросы Delphi 16 22.08.2010 12:12
Изменение кнопки при навдении указателя мыши Syltan JavaScript, Ajax 3 25.05.2010 15:41
Увеличение изображения при наведении указателя Savato HTML и CSS 2 07.10.2009 22:39
Изменение вида Image при наведении указателя Сергей А Помощь студентам 3 05.08.2007 15:26