class вложенные - Плюсы и минусы использования вложенных классов C++и перечислений?



классы доступ (12)

Каковы плюсы и минусы использования вложенных открытых классов C ++ и перечислений? Например, предположим, что у вас есть класс под названием printer , и этот класс также хранит информацию о выходных лотках, вы можете:

class printer
{
public:
    std::string name_;

    enum TYPE
    {
        TYPE_LOCAL,
        TYPE_NETWORK,
    };

    class output_tray
    {
        ...
    };
    ...
};

printer prn;
printer::TYPE type;
printer::output_tray tray;

В качестве альтернативы:

class printer
{
public:
    std::string name_;
    ...
};

enum PRINTER_TYPE
{
    PRINTER_TYPE_LOCAL,
    PRINTER_TYPE_NETWORK,
};

class output_tray
{
    ...
};

printer prn;
PRINTER_TYPE type;
output_tray tray;

Я вижу преимущества вложенности частных enums / classes, но когда дело доходит до публичных, офис разделен - кажется, это скорее выбор стиля.

Итак, что вы предпочитаете и почему?


Answers

Помните, что вы всегда можете продвигать вложенный класс на верхний уровень позже, но вы не сможете сделать обратное, не нарушая существующий код. Поэтому мой совет должен сделать его вложенным классом первым, и если он начнет становиться проблемой, сделайте его класс верхнего уровня в следующей версии.


Вложенные классы

Есть несколько побочных эффектов для классов, вложенных внутри классов, которые я обычно рассматриваю как недостатки (если не чистые антипаттерны).

Представим следующий код:

class A
{
   public :
      class B { /* etc. */ } ;

   // etc.
} ;

Или даже:

class A
{
   public :
      class B ;

   // etc.
} ;

class A::B
{
   public :

   // etc.
} ;

Так:

  • Привилегированный доступ: A :: B имеет привилегированный доступ ко всем членам A (методы, переменные, символы и т. Д.), Что ослабляет инкапсуляцию
  • Область A является кандидатом для поиска символа: код изнутри B будет видеть все символы из A в качестве возможных кандидатов для поиска символа, что может смутить код
  • forward-declaration: нет возможности переадресовывать A :: B без полного объявления A
  • Расширяемость: невозможно добавить еще один класс A :: C, если вы не являетесь владельцем A
  • Многословность кода: помещение классов в классы только увеличивает количество заголовков. Вы все же можете разделить это на несколько объявлений, но нет возможности использовать псевдонимы, импорты или использование имен, подобных пространству имен.

Как вывод, если исключения (например, вложенный класс является интимной частью класса вложенности ... И даже тогда ...), я не вижу смысла в вложенных классах в нормальном коде, поскольку недостатки перевесов по величинам воспринимаются преимуществами ,

Кроме того, он пахнет как неуклюжая попытка имитировать пространство имен без использования пространств имен C ++.

На стороне pro, вы изолируете этот код, и если он закрыт, сделайте его непригодным, но из класса «снаружи» ...

Вложенные перечисления

Плюсы: Все.

Кон: Ничего.

Тот факт, что элементы перечисления будут загрязнять глобальный масштаб:

// collision
enum Value { empty = 7, undefined, defined } ;
enum Glass { empty = 42, half, full } ;

// empty is from Value or Glass?

Ony, помещая каждое перечисление в другое пространство имен / класс, позволит вам избежать этого столкновения:

namespace Value { enum type { empty = 7, undefined, defined } ; }
namespace Glass { enum type { empty = 42, half, full } ; }

// Value::type e = Value::empty ;
// Glass::type f = Glass::empty ;

Обратите внимание, что C ++ 0x определяет перечисление класса:

enum class Value { empty, undefined, defined } ;
enum class Glass { empty, half, full } ;

// Value e = Value::empty ;
// Glass f = Glass::empty ;

именно для таких проблем.


Если вы поместите enum в класс или пространство имен, intellisense сможет дать вам указания, когда вы пытаетесь запомнить имена перечислений. Маленькая вещь наверняка, но иногда мелочи имеют значение.


Я согласен с сообщениями, защищающими вложение вашего перечисления в класс, но бывают случаи, когда имеет смысл не делать этого (но, пожалуйста, по крайней мере, поместить его в пространство имен). Если несколько классов используют перечисление, определенное в другом классе, то эти классы напрямую зависят от этого другого конкретного класса (которому принадлежит перечисление). Это, безусловно, представляет собой недостаток дизайна, поскольку этот класс будет отвечать за это перечисление, а также за другие обязанности.

Итак, да, вставьте enum в класс, если другой код использует только это перечисление для непосредственного взаимодействия с этим конкретным классом. В противном случае найдите лучшее место для хранения перечисления, такого как пространство имен.


Если вы никогда не будете использовать зависимый класс для чего-либо, кроме работы с реализациями независимого класса, вложенные классы, на мой взгляд, прекрасны.

Это когда вы хотите использовать «внутренний» класс как самостоятельный объект, который может начать становиться немного манкированным, и вы должны начать писать процедуры экстрактора / вставки. Некрасивая ситуация.


paercebal сказал все, что я сказал бы о вложенных перечислениях.

WRT вложенные классы, мой общий и почти единственный случай использования для них - это когда у меня есть класс, который управляет определенным типом ресурса, и мне нужен класс данных, который представляет что-то конкретное для этого ресурса. В вашем случае output_tray может быть хорошим примером, но обычно я не использую вложенные классы, если класс будет иметь какие-либо методы, которые будут вызываться из-за класса, содержащего класс, или это больше, чем прежде всего класс данных. Обычно я также не вставляю классы данных, если только содержащийся класс никогда не ссылается напрямую за класс, содержащий класс.

Так, например, если бы у меня был класс printer_manipulator, он мог бы содержать класс для ошибок манипулирования принтером, но сам принтер был бы не содержащим классов.

Надеюсь это поможет. :)


Только проблема с вложенными классами, с которой я столкнулась, заключалась в том, что C ++ не позволяет ссылаться на объект охватывающего класса во вложенных функциях класса. Мы не можем сказать «Enclosing :: this»

(Но, может быть, есть способ?)


Для меня большой конфликт с внешним миром заключается в том, что он становится частью глобального пространства имен. Если перечисление или связанный класс действительно применимы только к классу, в котором он находится, тогда это имеет смысл. Таким образом, в принтере все, что включает принтер, будет знать о наличии полного доступа к enum PRINTER_TYPE, где это действительно не нужно знать об этом. Я не могу сказать, что я когда-либо использовал внутренний класс, но для перечисления это кажется более логичным, чтобы держать его внутри. Как указывал еще один плакат, также неплохо использовать пространства имен для группировки подобных элементов, так как засорение глобального пространства имен действительно может быть плохой. Я ранее работал над крупными проектами, и только для создания полного полного списка в глобальном пространстве имен требуется 20 минут. По-моему, вложенные enums и namespaced classes / structs, вероятно, являются самым чистым подходом.


Visual Studio 2008, похоже, не может обеспечить intellisense для вложенных классов, поэтому я перешел на идиому PIMPL в большинстве случаев, когда у меня был вложенный класс. Я всегда помещаю перечисления в класс, если он используется только этим классом или вне класса в том же пространстве имен, что и класс, когда более одного класса использует перечисление.


Похоже, вы должны использовать пространства имен вместо классов для группировки, как вещи, которые связаны друг с другом таким образом. Один из кодов, который я мог видеть при работе с вложенными классами, заключается в том, что у вас действительно большой исходный файл, который трудно найти, когда вы ищете раздел.


Я вижу con для вложенных классов, что лучше использовать универсальное программирование.

Если маленький класс определен за пределами большого, вы можете сделать большой класс шаблоном класса и использовать любой «маленький» класс, который вам может понадобиться в будущем с большим классом.

Общее программирование - мощный инструмент, и, IMHO, мы должны помнить об этом при разработке расширяемых программ. Странно, что никто не упомянул об этом.


Статические и классовые методы

Как отмечали другие ответы, статические и классные методы легко выполняются с использованием встроенных декораторов:

class Test(object):

    # regular instance method:
    def MyMethod(self):
        pass

    # class method:
    @classmethod
    def MyClassMethod(klass):
        pass

    # static method:
    @staticmethod
    def MyStaticMethod():
        pass

Как обычно, первый аргумент MyMethod() привязан к объекту экземпляра класса. Напротив, первый аргумент MyClassMethod() привязан к самому объекту класса (например, в данном случае Test ). Для MyStaticMethod() ни один из аргументов не связан, и аргументы вообще необязательны.

«Статические переменные»

Однако реализация «статических переменных» (ну, изменяемые статические переменные, во всяком случае, если это не противоречие в терминах ...) не так прямолинейна. Как отметил Миллердев в своем ответе , проблема в том, что атрибуты класса Python не являются действительно «статическими переменными». Рассматривать:

class Test(object):
    i = 3  # This is a class attribute

x = Test()
x.i = 12   # Attempt to change the value of the class attribute using x instance
assert x.i == Test.i  # ERROR
assert Test.i == 3    # Test.i was not affected
assert x.i == 12      # x.i is a different object than Test.i

Это связано с тем, что строка xi = 12 добавила новый атрибут экземпляра от i до x вместо изменения значения атрибута Test class i .

Частичное ожидаемое поведение статической переменной, т. Е. Синхронизация атрибута между несколькими экземплярами (но не с самим классом, см. «Gotcha» ниже), может быть достигнута путем превращения атрибута class в свойство:

class Test(object):

    _i = 3

    @property
    def i(self):
        return type(self)._i

    @i.setter
    def i(self,val):
        type(self)._i = val

## ALTERNATIVE IMPLEMENTATION - FUNCTIONALLY EQUIVALENT TO ABOVE ##
## (except with separate methods for getting and setting i) ##

class Test(object):

    _i = 3

    def get_i(self):
        return type(self)._i

    def set_i(self,val):
        type(self)._i = val

    i = property(get_i, set_i)

Теперь вы можете сделать:

x1 = Test()
x2 = Test()
x1.i = 50
assert x2.i == x1.i  # no error
assert x2.i == 50    # the property is synced

Статическая переменная теперь будет синхронизироваться между всеми экземплярами класса .

(ПРИМЕЧАНИЕ. То есть, если экземпляр класса не решает определить свою собственную версию _i ! Но если кто-то решает это сделать, они заслуживают того, что получают, не так ли?)

Обратите внимание, что технически говоря, i по-прежнему не является «статической переменной»; это property , которое является особым типом дескриптора. Однако поведение property теперь эквивалентно (изменяемой) статической переменной, синхронизированной по всем экземплярам класса.

Неизменяемые «статические переменные»

Для неизменного поведения статической переменной просто опустите property setter:

class Test(object):

    _i = 3

    @property
    def i(self):
        return type(self)._i

## ALTERNATIVE IMPLEMENTATION - FUNCTIONALLY EQUIVALENT TO ABOVE ##
## (except with separate methods for getting i) ##

class Test(object):

    _i = 3

    def get_i(self):
        return type(self)._i

    i = property(get_i)

Теперь попытка установить атрибут экземпляра i вернет AttributeError :

x = Test()
assert x.i == 3  # success
x.i = 12         # ERROR

Один Gotcha должен знать

Обратите внимание, что вышеупомянутые методы работают только с экземплярами вашего класса - они не будут работать при использовании самого класса . Так, например:

x = Test()
assert x.i == Test.i  # ERROR

# x.i and Test.i are two different objects:
type(Test.i)  # class 'property'
type(x.i)     # class 'int'

Строка assert Test.i == xi вызывает ошибку, так как атрибут i Test и x - это два разных объекта.

Многие люди посчитают это удивительным. Однако этого не должно быть. Если мы вернемся и проверим наше определение класса Test (вторая версия), мы обратим внимание на эту строку:

    i = property(get_i) 

Очевидно, что член i Test должен быть объектом property , который является типом объекта, возвращаемого функцией property .

Если вы считаете, что это запутанное, вы, скорее всего, все еще думаете об этом с точки зрения других языков (например, Java или c ++). Вы должны изучить объект property , порядок, в котором возвращаются атрибуты Python, протокол дескриптора и порядок разрешения метода (MRO).

Я представляю решение вышеприведенного «gotcha» ниже; однако я бы предложил - усиленно - чтобы вы не пытались сделать что-то вроде следующего, пока, как минимум, вы не поймете, почему assert Test.i = xi вызывает ошибку.

REAL, ACTUAL Статические переменные - Test.i == xi

Я представляю решение (Python 3) ниже только для информационных целей. Я не одобряю это как «хорошее решение». У меня есть сомнения относительно того, действительно ли необходимо использовать эмуляцию поведения статических переменных для других языков в Python. Однако, независимо от того, действительно ли это полезно, нижеследующее должно помочь в дальнейшем понимании того, как работает Python.

ОБНОВЛЕНИЕ: эта попытка действительно очень ужасна ; если вы настаиваете на том, чтобы делать что-то вроде этого (подсказка: пожалуйста, не делайте: Python - очень элегантный язык, а обувь - это то, что он ведет себя как другой язык, просто не нужен), вместо этого используйте код в ответе Итана Фурмана .

Эмулирование поведения статических переменных других языков с использованием метакласса

Метакласс - это класс класса. Метакласс по умолчанию для всех классов в Python (т. Е. Классы «нового стиля» post Python 2.3, на которые я верю) является type . Например:

type(int)  # class 'type'
type(str)  # class 'type'
class Test(): pass
type(Test) # class 'type'

Однако вы можете определить свой собственный метакласс следующим образом:

class MyMeta(type): pass

И примените его к вашему собственному классу, как это (только для Python 3):

class MyClass(metaclass = MyMeta):
    pass

type(MyClass)  # class MyMeta

Ниже представлен метакласс, который я попытался подражать «статической переменной» поведения других языков. Он в основном работает, заменяя по умолчанию getter, setter и deleter версиями, которые проверяют, является ли запрашиваемый атрибут «статической переменной».

Каталог «статических переменных» хранится в StaticVarMeta.statics . Все запросы атрибутов изначально пытались разрешить с использованием альтернативного порядка разрешения. Я назвал это «порядком статического разрешения» или «SRO». Это делается путем поиска запрашиваемого атрибута в наборе «статических переменных» для данного класса (или его родительских классов). Если атрибут не отображается в «SRO», класс вернется к положению атрибута get / set / delete по умолчанию (т. Е. «MRO»).

from functools import wraps

class StaticVarsMeta(type):
    '''A metaclass for creating classes that emulate the "static variable" behavior
    of other languages. I do not advise actually using this for anything!!!

    Behavior is intended to be similar to classes that use __slots__. However, "normal"
    attributes and __statics___ can coexist (unlike with __slots__). 

    Example usage: 

        class MyBaseClass(metaclass = StaticVarsMeta):
            __statics__ = {'a','b','c'}
            i = 0  # regular attribute
            a = 1  # static var defined (optional)

        class MyParentClass(MyBaseClass):
            __statics__ = {'d','e','f'}
            j = 2              # regular attribute
            d, e, f = 3, 4, 5  # Static vars
            a, b, c = 6, 7, 8  # Static vars (inherited from MyBaseClass, defined/re-defined here)

        class MyChildClass(MyParentClass):
            __statics__ = {'a','b','c'}
            j = 2  # regular attribute (redefines j from MyParentClass)
            d, e, f = 9, 10, 11   # Static vars (inherited from MyParentClass, redefined here)
            a, b, c = 12, 13, 14  # Static vars (overriding previous definition in MyParentClass here)'''
    statics = {}
    def __new__(mcls, name, bases, namespace):
        # Get the class object
        cls = super().__new__(mcls, name, bases, namespace)
        # Establish the "statics resolution order"
        cls.__sro__ = tuple(c for c in cls.__mro__ if isinstance(c,mcls))

        # Replace class getter, setter, and deleter for instance attributes
        cls.__getattribute__ = StaticVarsMeta.__inst_getattribute__(cls, cls.__getattribute__)
        cls.__setattr__ = StaticVarsMeta.__inst_setattr__(cls, cls.__setattr__)
        cls.__delattr__ = StaticVarsMeta.__inst_delattr__(cls, cls.__delattr__)
        # Store the list of static variables for the class object
        # This list is permanent and cannot be changed, similar to __slots__
        try:
            mcls.statics[cls] = getattr(cls,'__statics__')
        except AttributeError:
            mcls.statics[cls] = namespace['__statics__'] = set() # No static vars provided
        # Check and make sure the statics var names are strings
        if any(not isinstance(static,str) for static in mcls.statics[cls]):
            typ = dict(zip((not isinstance(static,str) for static in mcls.statics[cls]), map(type,mcls.statics[cls])))[True].__name__
            raise TypeError('__statics__ items must be strings, not {0}'.format(typ))
        # Move any previously existing, not overridden statics to the static var parent class(es)
        if len(cls.__sro__) > 1:
            for attr,value in namespace.items():
                if attr not in StaticVarsMeta.statics[cls] and attr != ['__statics__']:
                    for c in cls.__sro__[1:]:
                        if attr in StaticVarsMeta.statics[c]:
                            setattr(c,attr,value)
                            delattr(cls,attr)
        return cls
    def __inst_getattribute__(self, orig_getattribute):
        '''Replaces the class __getattribute__'''
        @wraps(orig_getattribute)
        def wrapper(self, attr):
            if StaticVarsMeta.is_static(type(self),attr):
                return StaticVarsMeta.__getstatic__(type(self),attr)
            else:
                return orig_getattribute(self, attr)
        return wrapper
    def __inst_setattr__(self, orig_setattribute):
        '''Replaces the class __setattr__'''
        @wraps(orig_setattribute)
        def wrapper(self, attr, value):
            if StaticVarsMeta.is_static(type(self),attr):
                StaticVarsMeta.__setstatic__(type(self),attr, value)
            else:
                orig_setattribute(self, attr, value)
        return wrapper
    def __inst_delattr__(self, orig_delattribute):
        '''Replaces the class __delattr__'''
        @wraps(orig_delattribute)
        def wrapper(self, attr):
            if StaticVarsMeta.is_static(type(self),attr):
                StaticVarsMeta.__delstatic__(type(self),attr)
            else:
                orig_delattribute(self, attr)
        return wrapper
    def __getstatic__(cls,attr):
        '''Static variable getter'''
        for c in cls.__sro__:
            if attr in StaticVarsMeta.statics[c]:
                try:
                    return getattr(c,attr)
                except AttributeError:
                    pass
        raise AttributeError(cls.__name__ + " object has no attribute '{0}'".format(attr))
    def __setstatic__(cls,attr,value):
        '''Static variable setter'''
        for c in cls.__sro__:
            if attr in StaticVarsMeta.statics[c]:
                setattr(c,attr,value)
                break
    def __delstatic__(cls,attr):
        '''Static variable deleter'''
        for c in cls.__sro__:
            if attr in StaticVarsMeta.statics[c]:
                try:
                    delattr(c,attr)
                    break
                except AttributeError:
                    pass
        raise AttributeError(cls.__name__ + " object has no attribute '{0}'".format(attr))
    def __delattr__(cls,attr):
        '''Prevent __sro__ attribute from deletion'''
        if attr == '__sro__':
            raise AttributeError('readonly attribute')
        super().__delattr__(attr)
    def is_static(cls,attr):
        '''Returns True if an attribute is a static variable of any class in the __sro__'''
        if any(attr in StaticVarsMeta.statics[c] for c in cls.__sro__):
            return True
        return False




c++ class enums nested