C ++ Доступ к членам экземпляра через область видимости класса - PullRequest
1 голос
/ 06 марта 2012

Я портирую приложение с Windows на Linux. Один компонент читает структурированные данные из файла.

Пример ввода: # 10 = CLOSED_POCKET (2.0, CARPET);

Для каждой возможной сущности соответствующий класс c ++ генерируется из определения типа. Фабрика создает соответствующий объект в зависимости от имени объекта (т.е. CLOSED_POCKET). После этого атрибуты читаются один за другим. Поэтому мы хотим назначить членов класса c ++ через индекс текущего атрибута.

Код корректно работает в Windows, скомпилированной с Visual Studio 2010. Я перенес код в Linux 10.04 (Lucid Lynx) и успешно скомпилировал его с помощью gcc 4.4.6 в Eclipse CDT Indigo.

Проблема в Linux: Когда я обращаюсь к методам атрибутов, отладчик иногда переходит к неправильным функциям (соответственно, смещение функции неверно при вызове функции подклассов), что приводит к ошибке сегментации.

Я сделал минимальный пример, который также приводит к ошибке сегментации (см. Ниже).

Мой вопрос теперь: когда Windows может успешно запустить его, что мне нужно сделать, чтобы запустить его под Linux с GCC?

Я знаю, что Downcasting виртуальных унаследованных классов недопустим согласно стандарту c ++ (см. Downcast в алмазной иерархии ), но, может быть, существует другое решение для доступа к членам экземпляров через класс. Виртуальное наследование необходимо из-за структуры объектов, указанных в стандарте ISO.

Я также думал о предоставлении массива доступа (MemberPtrArray) для каждого экземпляра, но при прочтении около 80 000 объектов доступ к области видимости класса был бы лучше.

/*
 * MemberPointerTest.h
 */

#ifndef MAINTEST_H_
#define MAINTEST_H_

#include <string>

class BaseAttribute{
public:
    virtual void SetReal(double value);
    virtual void SetSelectName(std::string selectName);
};
class RealAttribute : public BaseAttribute{
public:
    double value;
    virtual void SetReal(double value);
};
class SelectAttribute: public BaseAttribute{
public:
    std::string selectName;
    virtual void SetSelectName(std::string selectName);
};

class BaseEntity{
public:
    BaseAttribute id;
    virtual ~BaseEntity(){}
};
class PocketEntity : virtual public BaseEntity{
public:
    RealAttribute depth;
};
class ClosedPocketEntity : virtual public PocketEntity{
public:
    SelectAttribute surfaceType;
    static BaseAttribute ClosedPocketEntity::* memberPtrArray[3];
    BaseAttribute* GetMember(unsigned int index);
};

#endif 



/* 
 * MemberPointerTest.cpp
 */

#include "MemberPointerTest.h"

void BaseAttribute::SetReal(double value){

}
void BaseAttribute::SetSelectName(std::string selectName){

}

void RealAttribute::SetReal(double value){
    this->value = value;
}
void SelectAttribute::SetSelectName(std::string selectName){
    this->selectName = selectName;
}

BaseAttribute ClosedPocketEntity::* ClosedPocketEntity::memberPtrArray[] = {
        (BaseAttribute ClosedPocketEntity::*) &PocketEntity::depth,
        (BaseAttribute ClosedPocketEntity::*) &ClosedPocketEntity::surfaceType
};
/* Tried the following alternatives:
*  &PocketEntity::depth, // cannot convert ‘RealAttribute PocketEntity::*’ to ‘BaseAttribute ClosedPocketEntity::*’ in initialization
*  (RealAttribute ClosedPocketEntity::*) &ClosedPocketEntity::depth, // invalid conversion from ‘RealAttribute ClosedPocketEntity::*’ to ‘BaseAttribute ClosedPocketEntity::*’
*/

BaseAttribute* ClosedPocketEntity::GetMember(unsigned int index){
    return &(this->*memberPtrArray[index]);
}


int main(){
    ClosedPocketEntity cpEntity;

    // Case 1: Calls SetReal of BaseAttribute
    BaseAttribute* depthPtr = cpEntity.GetMember(0);
    depthPtr->SetReal(3.0);

    // Case 2: Produces Segmentation fault
    RealAttribute* depthPtr2 = dynamic_cast<RealAttribute*>(cpEntity.GetMember(0));
    depthPtr2->SetReal(2.0); // SIGSEGV

    return 0;

}

1 Ответ

2 голосов
/ 06 марта 2012
BaseAttribute ClosedPocketEntity::* ClosedPocketEntity::memberPtrArray[] = {
        (BaseAttribute ClosedPocketEntity::*) &PocketEntity::depth,
        (BaseAttribute ClosedPocketEntity::*) &ClosedPocketEntity::surfaceType
};

Первое преобразование указателя, которое вы вводите здесь, недопустимо. Из C ++ 03 §4.11 / 2 Указатель на преобразование члена :

Значение типа «указатель на член B типа cv T», где B - тип класса, может быть преобразовано в значение типа «указатель на член D типа cv T», где D - это тип производный класс (пункт 10) из B. Если B - недоступный (пункт 11), неоднозначный (10.2) или виртуальный (10.1) базовый класс D, программа, для которой это необходимо Конверсия неправильная.

(Насколько я могу судить, формулировка в C ++ 11 не изменилась.)

&PocketEntity::depth относится к типу RealAttribute PocketEntity::*, поэтому даже преобразование в RealAttribute ClosedPocketEntity::* будет некорректным, поскольку PocketEntity является виртуальной базой ClosedPocketEntity.

clang++ имеет это полезное сообщение об ошибке:

error: conversion from pointer to member of class 'PocketEntity'
  to pointer to member of class 'ClosedPocketEntity'
  via virtual base 'PocketEntity' is not allowed

Если вы удалили виртуальное наследование, преобразование по-прежнему недействительно в соответствии с GCC и clang:

error: cannot initialize an array element of type
 'BaseAttribute ClosedPocketEntity::*'
with an rvalue of type
 'RealAttribute PocketEntity::*'

Ничто из того, что я вижу в этом разделе стандарта, не позволило бы это преобразование (но учтите, что я здесь не в своей тарелке и вполне может что-то упустить в замечательных правилах преобразования C ++).

Компилятор, который вы использовали в Windows, допускает это как расширение или просто «делает то, что вы хотите» в этом случае. Другие компиляторы, похоже, имеют дело с этим принудительным неверным приведением.

Что касается того, как это исправить, боюсь, я понятия не имею. (Вы уверены, что вам нужен такой замысловатый дизайн?)

...