Modificar const
variáveis ou métodos para informar ao compilador que eles são imutáveis ajuda o compilador a otimizar o código e ajuda os desenvolvedores a entender se a função tem efeitos colaterais. Além disso, using const &
evita que o compilador copie dados desnecessários. Vale a pena ler o comentário de John Carmack sobre ```const```[2].
// Bad Idea
class MyClass
{
public:
void do_something(int i);
void do_something(std::string str);
};
// Good Idea
class MyClass
{
public:
void do_something(const int i);
void do_something(const std::string &str);
};
Pense cuidadosamente sobre os tipos de retorno
-
Getters (API de leitura de variável de membro)
-
Em circunstâncias normais, ao ler variáveis de membro por meio de valores de retorno, usar
&
ouconst &
retornar valores pode melhorar significativamente o desempenho. -
Retornar por valor é mais propício à segurança do thread. Se o valor retornado for para cópia e uso, não haverá perda de desempenho.
-
Se o valor de retorno da API usar tipos de retorno covariantes, ele deverá retornar
&
ou*
-
-
Valores temporários e locais
-
Sempre retornado por valor
-
Não use referências const para passar e retornar tipos simples
// Very Bad Idea
class MyClass
{
public:
explicit MyClass(const int& t_int_value)
: m_int_value(t_int_value)
{
}
const int& get_int_value() const
{
return m_int_value;
}
private:
int m_int_value;
}
Em vez disso, tipos simples são passados e retornados por valor. Se você não planeja alterar os valores passados, declare-os como const
, mas não como const
referências:
// Good Idea
class MyClass
{
public:
explicit MyClass(const int t_int_value)
: m_int_value(t_int_value)
{
}
int get_int_value() const
{
return m_int_value;
}
private:
int m_int_value;
}
Porque isto é assim? Como a passagem e o retorno por referência resultam em operações de ponteiro, a passagem por valor é tratada nos registradores do processador e é mais rápida.
Evite acessar a memória bruta
É difícil em C++ lidar corretamente com acesso, alocação e desalocação de memória bruta sem o risco de erros e vazamentos de memória [3], e C++ 11 fornece ferramentas para evitar esses problemas.
// Bad Idea
MyClass *myobj = new MyClass;
// ...
delete myobj;
// Good Idea
auto myobj = std::make_unique<MyClass>(constructor_param1, constructor_param2); // C++14
auto myobj = std::unique_ptr<MyClass>(new MyClass(constructor_param1, constructor_param2)); // C++11
auto mybuffer = std::make_unique<char[]>(length); // C++14
auto mybuffer = std::unique_ptr<char[]>(new char[length]); // C++11
// or for reference counted objects
auto myobj = std::make_shared<MyClass>();
// ...
// myobj is automatically freed for you whenever it is no longer used.
Use std::array
ou std::vector
em vez de matrizes estilo C
Ambos os métodos garantem um layout de memória contíguo de objetos e podem (e devem) substituir completamente os arrays no estilo C, e são um dos muitos motivos para não usar ponteiros brutos.
Além disso, evite usar ```std::shared_ptr``` para salvar arrays[4].
Usar exceção
Os valores de retorno (por exemplo boost::optional
), podem ser ignorados e podem causar travamentos ou erros de memória se não forem verificados, enquanto as exceções não podem ser ignoradas. As exceções, por outro lado, podem ser capturadas e tratadas. É possível que a exceção suba até o nível mais alto da aplicação, onde será capturada, registrada no log e acione o reinício automático da aplicação.
Stroustrup, um dos designers de C++, falou sobre este tópico: Por que usar exceções?[5]
Use conversões no estilo C++ em vez de conversões no estilo C
Substitua as conversões no estilo C por conversões no estilo C++ ( static_cast<>
, dynamic_cast<>
, ...), que permitem mais verificações do compilador e são bastante seguras.
// Bad Idea
double x = getX();
int i = (int) x;
// Not a Bad Idea
int i = static_cast<int>(x);
Além disso, o estilo de conversão de tipo C++ é mais explícito e mais fácil de pesquisar.
Mas se você precisar double
converter tipos em int
tipos, considere refatorar a lógica do seu programa (por exemplo, verificações adicionais de overflow e underflow). Evite a situação de medir 3 vezes e depois cortar 0,9999999999981 vezes.
Não defina funções variáveis
Uma função variada pode aceitar um número variável de argumentos, sendo talvez o exemplo mais famoso printf()
. Embora seja possível definir tais funções, pode haver riscos de segurança. O uso de funções variadas não é seguro para tipos e parâmetros de entrada incorretos podem fazer com que o programa termine com comportamento indefinido. Este comportamento indefinido pode causar problemas de segurança. Se você usar um compilador que suporte C++1, poderá usar modelos variados.