Especificaciones comunes de C ++ (dos)

1. El estilo de código de la clase

2. Constructor, destructor, constructor de copias, operador de asignación

3. Herencia

4. Herencia múltiple


1. El estilo de código de la clase

  •   El orden de declaración del bloque de control de acceso de clase es public :, protected :, private :, sangría y alineación de palabras clave de clase
class MyClass : public BaseClass {
public: // 注意没有缩进
    MyClass(); // 标准的4空格缩进
    explicit MyClass(int var);
    ~MyClass() {}
    void SomeFunction();
    void SomeFunctionThatDoesNothing()
    {
    }
    void SetVar(int var) { someVar = var; }
    int GetVar() const { return someVar; }
private:
    bool SomeInternalFunction();
    int someVar;
    int someOtherVar;
};
  •  La lista de inicialización del constructor se coloca en la misma línea o se sangra con cuatro celdas y se organiza en varias líneas.
// 如果所有变量能放在同一行:
MyClass::MyClass(int var) : someVar(var)
{
    DoSomething();
}
// 如果不能放在同一行,
// 必须置于冒号后, 并缩进4个空格
MyClass::MyClass(int var)
    : someVar(var), someOtherVar(var + 1) // Good: 逗号后面留有空格
{
    DoSomething();
}
// 如果初始化列表需要置于多行, 需要逐行对齐
MyClass::MyClass(int var)
    : someVar(var), // 缩进4个空格
    someOtherVar(var + 1)
{
    DoSomething();
}
  • Use espacios de nombres para limitar el alcance
namespace Switcher {
    class Packet {
        ...
    }
    void SendPacket(const Packet& packet);
}

namespace Router {
    class Packet{
        ...
    }
    void SendPacket(const Packet& packet);
}

    Debido a que las variables globales, las constantes globales y las definiciones de tipos globales pertenecen al ámbito global, el uso de bibliotecas de terceros en el proyecto es propenso a generar conflictos. El espacio de nombres subdivide el ámbito en ámbitos con nombre independientes, lo que puede evitar eficazmente el nombramiento de conflicto de alcance global. 

2. Constructor, destructor, constructor de copias, operador de asignación

  • Las variables miembro de la clase deben inicializarse explícitamente
  • Las variables miembro se priorizan mediante la inicialización en la declaración (C ++ 11) y la inicialización de la lista de inicialización del constructor
  • Para evitar la conversión implícita, el constructor de un solo parámetro se declara como explícito
class Foo {
public:
    explicit Foo(const string& name): name(name)
    {
    }
private:
    string name;
};

void ProcessFoo(const Foo& foo){}

int main(void)
{
    std::string test = "test";
    ProcessFoo(test); // 编译不通过
    return 0;
}

     El código anterior no se puede compilar porque el parámetro que ProcessFoo requiere es de tipo Foo y el tipo de cadena pasada no coincide. Si se elimina la palabra clave explícita del constructor Foo, la cadena pasada cuando se llama a ProcessFoo activará una conversión implícita y generará un objeto Foo temporal. A menudo, este tipo de conversión implícita es confuso y es fácil ocultar errores, lo que resulta en una conversión de tipo inesperada. Por lo tanto, se requiere una declaración explícita para un constructor de un solo parámetro.

  • Si no necesita las funciones de copiar / mover, prohíbalas explícitamente. Si no lo define el usuario, el compilador generará un constructor de copia y un operador de asignación de copia de forma predeterminada.
class Foo {
private:
    Foo(const Foo&);
    Foo& operator=(const Foo&);
}; 
/* 将拷贝构造或者复制操作符设置为private,并且不实现 */
  • Los operadores de copia de construcción y asignación de copia deben estar emparejados o prohibidos 
// 同时出现
class Foo {
public:
    ...
    Foo(const Foo&);
    Foo& operator=(const Foo&);
    ...
};
// 同时default, C++11支持
class Foo {
public:
    Foo(const Foo&) = default;
    Foo& operator=(const Foo&) = default;
};
// 同时禁止, C++11可以使用delete
class Foo {
private:
    Foo(const Foo&);
    Foo& operator=(const Foo&);
};
// 使用delete
class Foo {
public:
    Foo(const Foo&) = delete;
    Foo& operator=(const Foo&) = delete;
}
  •  Está prohibido llamar a funciones virtuales en el constructor y el destructor, lo que conducirá a un comportamiento polimórfico no implementado.
class Base {
public:
    Base();
    virtual void Log() = 0; // 不同的派生类调用不同的日志文件
};

Base::Base() // 基类构造函数
{
    Log(); // 调用虚函数Log
}

class Sub : public Base {
public:
    virtual void Log();
};

     Cuando se ejecuta la siguiente instrucción: Sub sub; El constructor de Sub se ejecutará primero, pero se llamará primero al constructor de Base. Dado que el constructor de Base llama a la función virtual Log, Log sigue siendo la versión de la clase base. Solo después de que se complete la construcción de la clase base, completará la construcción de la clase derivada, lo que dará como resultado un comportamiento polimórfico no cumplido. El mismo principio se aplica a los destructores.

3. Herencia

  • El destructor de la clase base debe declararse como virtual. Solo el destructor de la clase base es virtual, y se puede garantizar que se llamará al destructor de la clase derivada cuando se llame mediante polimorfismo:
class Base {
public:
    virtual std::string getVersion() = 0;
    ~Base()
    {
        std::cout << "~Base" << std::endl;
    }
};

class Sub : public Base {
public:
    Sub() : numbers(NULL)
    {
    }
    ~Sub()
    {
        delete[] numbers;
        std::cout << "~Sub" << std::endl;
    }
    int Init()
    {
        const size_t numberCount = 100;
        numbers = new (std::nothrow) int[numberCount];
        if (numbers == NULL) {
            return -1;
        }
        ...
    }
    std::string getVersion()
    {
        return std::string("hello!");
    }
private:
    int* numbers;
};

int main(int argc, char* args[])
{
    Base* b = new Sub();
    delete b;
    return 0;
}

     Dado que el destructor de la clase base Base no se declara como virtual, cuando se destruye el objeto, solo se llamará al destructor de la clase base y no se llamará al destructor de la clase derivada Sub, lo que provocará una pérdida de memoria.

 Nota : Siempre que elimine una subclase, se llamará al destructor de la clase principal . En cuanto a si el destructor es una función virtual, es principalmente para asegurar que el destructor de la subclase se pueda llamar correctamente cuando se borre el puntero de la clase principal y el puntero apunte al objeto de la subclase. Al eliminar un objeto de subclase mediante el puntero del objeto de subclase, no importa si el destructor de la clase principal es virtual o no, se llamará al destructor de la clase principal. Pero al eliminar el objeto a través del puntero del objeto de la clase principal (apuntando al objeto de la subclase), si el destructor de la clase principal no es virtual, no se llamará al destructor de la subclase. Entonces, para garantizar la corrección, el destructor (es la clase base) de la clase derivada debe declararse como virtual.

  • Las funciones virtuales tienen prohibido utilizar valores de parámetros predeterminados. Las funciones virtuales están vinculadas dinámicamente, pero los parámetros predeterminados de la función están vinculados estáticamente en el momento de la compilación. Esto significa que la función que finalmente ejecuta es una función virtual que se define en la clase derivada, pero usa los valores de parámetro predeterminados en la clase base.
class Base {
public:
    virtual void Display(const std::string& text = "Base!")
    {
        std::cout << text << std::endl;
    }
    virtual ~Base(){}
};

class Sub : public Base {
public:
    virtual void Display(const std::string& text = "Sub!")
    {
        std::cout << text << std::endl;
    }
    virtual ~Sub(){}
};

int main()
{
    Base* base = new Sub();
    Sub* sub = new Sub();
    ...
    base->Display(); // 程序输出结果: Base! 而期望输出:Sub!
    sub->Display(); // 程序输出结果: Sub!
    delete base;
    delete sub;
    return 0;
};
  •  Utilice la palabra clave override cuando reescriba funciones virtuales. La palabra clave override garantiza que la función es una función virtual y reemplaza la función virtual de la clase base. Si el prototipo de la función de subclase no es coherente con el de la función de clase base, se genera una advertencia de compilación.
class Base {
public:
    virtual void Foo();
    void Bar();
};

class Derived : public Base {
public:
    void Foo() const override; // 编译失败: derived::Foo 和 base::Foo 原型不一致,不是重写
    void Foo() override; // 正确: derived::Foo 重写 base::Foo
    void Bar() override; // 编译失败: base::Bar 不是虚函数
};

   1. La clase base define la función virtual por primera vez, usando la palabra clave virtual
   2. La subclase reescribe la función virtual de la clase base, usando la palabra clave override
   3. Funciones no virtuales, no se usa virtual ni override

  •  El comportamiento polimórfico debe ser válido en el caso de punteros o referencias, para las variables de valor, debe ser estático.
class CParent
{
public:
    CParent() {}
    virtual ~CParent() {}
public:
    virtual void Print()
    {
        std::cout << "1,";
    };
};

class CSon : public CParent
{
public:
    CSon() {};
    virtual ~CSon() {};
public:
    void Print()
    {
        std::cout << "2,";
    };
};

void Test1(CParent& oParent)\\传引用
{
    oParent.Print();
}

void Test2(CParent oParent)\\传值
{
    oParent.Print();
}

main()
{
    CSon * p = new CSon();
    Test1(*p);//调用子类的print方法
    Test2(*p); //调用父类的print方法
    delete p;
}
  • Si una función se declara como una función virtual en la clase padre, no importa si hay un hijo clave "vitrual" en la declaración de esta función en la subclase, es una función virtual. Incluso si los permisos de acceso cambian, incluido el destructor es el mismo.
class A
{
public:
    virtual void test();
};
class B: public A
{
public:
    void test();
    ...
};
class C: public B
{
public:
    void test();
    ...
};
/* B类的test函数是虚函数,而C类的也是 */

class C: public B
{
private:
    void test();
    ...
};
/* 则test一样是虚函数. */
  •    El destructor solo necesita especificar la clase base como una función virtual, y todos los destructores de la clase derivada son funciones virtuales.
class Base
{
public:
	virtual ~Base()
	{
		std::cout << "base" << std::endl;
	}
};

class Derive1:public Base
{
public:
	~Derive1()
	{
		std::cout << "derive1" << std::endl;
	}
};

class Derive2:public Derive1
{
public:
	~Derive2()
	{
		std::cout << "Derive2" << std::endl;
	}
};

int main(void)
{
	Derive1 *a = new Derive2();
	delete a;
	return 0;
}
输出:
Derive2
derive1
base
  • Aunque el destructor de la clase base abstracta puede ser una función virtual pura, para crear una instancia de su objeto de clase derivada, aún se debe proporcionar el cuerpo de función del destructor en la clase base abstracta.

4. Herencia múltiple

    Hay relativamente pocos escenarios en los que se utilice la herencia múltiple en el proceso de desarrollo real, porque existen los siguientes problemas típicos en el uso de la herencia múltiple:

1. Duplicación de datos y ambigüedad de nombres provocada por la herencia de diamantes. Por lo tanto, C ++ introdujo la herencia virtual para resolver tales problemas;
2. Incluso si no es una herencia de diamante, puede haber conflictos entre los nombres de múltiples clases principales, lo que resulta en ambigüedad;
3. Si la subclase necesita ser extendida o reescrita más Cuando se utiliza el método de una clase padre, las responsabilidades de la subclase no están claras y la semántica está confusa;
4. En comparación con la delegación, la herencia es una especie de reutilización de caja blanca, es decir, la subclase puede acceder a los miembros protegidos del padre clase, lo que conducirá a un acoplamiento más fuerte. Y la herencia múltiple, debido al acoplamiento de varias clases principales, en comparación con la herencia de raíz única, producirá una relación de acoplamiento más fuerte.

    La herencia múltiple tiene las siguientes ventajas: La herencia múltiple proporciona una combinación más simple para realizar el ensamblaje y reutilización de múltiples interfaces o clases. 

Supongo que te gusta

Origin blog.csdn.net/MOU_IT/article/details/110693838
Recomendado
Clasificación