C ++ 11로 리팩토링
C ++ 많은 프로그래머가 제공하는 새로운 도구 세트를 감안할 때 코드 단순화, 표현력, 효율성, 이전 코드를 훑어보고 목표를 달성하기 위해 조정 (무의미하고 일부는 성공)합니다. 그러한 노동에 너무 많은 시간을 낭비하지 않고 방해하지 않고 자체적으로 변경하는 동안 최선의 방법은 무엇입니까?
명백한 점을 지우겠습니다.
반복기 기반 루프를 실행 하려면 auto 를 사용하십시오 .
for (std::vector<foo>::const_iterator it(lala.begin()), ite(lala.end()); it != ite; ++it); // becomes for (auto it(lala.cbegin()), ite(lala.cend()); it != ite; ++it);
C 스타일의 코드 행을 생성하는 여러 할당에 넥타이 를 사용하십시오 ( 한 번에 여러 값을 구조체에 할당하는 방법? )
a = 1; b = 2; c = 3; d = 4; e = 5; // becomes std::tie(a, b, c, d, e) = std::make_tuple(1, 2, 3, 4, 5);
클래스를 상속 불가능하게 만들려면 "최종"으로 선언하고 이러한 동작을 수행 한 코드를 삭제 하십시오. http://www.parashift.com/c++-faq/final-classes.html
생성자 / 소멸자를 비공개로 선언하는 대신 명시 적으로 숨기려면 delete 키워드를 사용하십시오 (예 : 힙 기반 개체, 복사 할 수없는 개체 등을 만드는 코드).
단일 STL 알고리즘의 실행을 람다 함수 로 용이하게하기 위해 만들어진 사소한 펑터를 람다 함수 로 전환 합니다 (코드 혼란을 줄이는 것 외에도 인라인 호출을 보장합니다).
스마트 포인터를 사용하여 객체의 RAII 래핑을 단순화합니다.
bind1st, bind2nd를 제거하고 bind를 사용하십시오.
유형 특성 (Is_ptr_but_dont_call_for_const_ptrs <> 등 :))에 대해 손으로 작성한 코드를 <type_traits>에서 제공하는 표준 코드로 대체합니다.
이제 STL (BOOST_STATIC_ASSERT 대 static_assert)에 구현 된 기능성에 대한 부스트 헤더 포함 중지
클래스에 이동 의미를 제공합니다 (더티 / 빠른 / 쉬운 변경으로 간주되지는 않지만)
가능한 경우 NULL 매크로 대신 nullptr을 사용 하고 포인터 컨테이너를 개체 유형으로 캐스팅 된 0으로 채운 코드를 제거합니다.
std::vector<foo*> f(23); for (std::size_t i(0); i < 23; ++i) { f[i] = static_cast<foo*>(0); } // becomes std::vector<foo*> f(23, nullptr);
벡터 데이터 액세스 구문 지우기
std::vector<int> vec; &vec[0]; // access data as a C-style array vec.data(); // new way of saying the above
throw ()를 noexcept 로 바꿉니다 (사용되지 않는 예외 사양을 피하는 것 외에는 속도 이점이 있습니다. http://channel9.msdn.com/Events/GoingNative/2013/An-Effective-Cpp11-14-Sampler @ 00.29.42)
void some_func() noexcept; // more optimization options void some_func() throw(); // fewer optimization options void some_func() ; // fewer optimization options
컨테이너에서 임시를 푸시 할 코드를 대체하고 옵티마이 저가 가능한 경우 "emplace" 함수를 사용 하여 복사본을 제거하기를 희망 하여 인수를 완벽하게 전달하고 임시없이 컨테이너로 객체를 직접 구성합니다. 모두.
vecOfPoints.push_back(Point(x,y,z)); // so '03 vecOfPoints.emplace_back(x, y, z); // no copy or move operations performed
최신 정보
Shafik Yaghmour의 대답 은 청중이 가장 많이 받아 들인 것에 대한 현상금을 정당하게 받았습니다.
R Sahu 가 제안한 기능 의 조합 은 코드를 더 명확하고 깨끗하고 간단하고 우아하게 만드는 리팩토링 의 정신을 포착 하기 때문에 제가 받아 들인 대답 이었습니다 .
위임 생성자와 클래스 내 멤버 이니셜 라이저를 목록에 추가합니다.
위임 생성자 및 클래스 내 초기화를 사용하여 단순화
C ++ 03 사용 :
class A
{
public:
// The default constructor as well as the copy constructor need to
// initialize some of the members almost the same and call init() to
// finish construction.
A(double data) : id_(0), name_(), data_(data) {init();}
A(A const& copy) : id_(0), name_(), data_(copy.data_) {init();}
void init()
{
id_ = getNextID();
name_ = getDefaultName();
}
int id_;
string name_;
double data_;
};
C ++ 11 사용 :
class A
{
public:
// With delegating constructor, the copy constructor can
// reuse this constructor and avoid repetitive code.
// In-line initialization takes care of initializing the members.
A(double data) : data_(data) {}
A(A const& copy) : A(copy.data_) {}
int id_ = getNextID();
string name_ = getDefaultName();
double data_;
};
1. 랜드 교체
C ++ 11의 큰 이점 중 하나는의 사용을 임의 헤더rand()
에서 사용할 수있는 모든 옵션 으로 대체하는 것입니다 . 대부분의 경우 교체 는 간단해야합니다.rand()
Stephan T. Lavavej 는 그의 프레젠테이션 rand ()는 Harmful 으로이 점을 가장 강하게 만들었습니다 . 예제는 다음을 [0,10]
사용하여 균일 한 정수 분포를 보여줍니다 rand()
.
#include <cstdlib>
#include <iostream>
#include <ctime>
int main()
{
srand(time(0)) ;
for (int n = 0; n < 10; ++n)
{
std::cout << (rand() / (RAND_MAX / (10 + 1) + 1)) << ", " ;
}
std::cout << std::endl ;
}
및 사용하여 표준 : uniform_int_distrubution를 :
#include <iostream>
#include <random>
int main()
{
std::random_device rd;
std::mt19937 e2(rd());
std::uniform_int_distribution<> dist(0, 10);
for (int n = 0; n < 10; ++n) {
std::cout << dist(e2) << ", " ;
}
std::cout << std::endl ;
}
이와 함께 std :: random_shuffle 에서 std :: shuffle 로 이동해야합니다. 이는 Deprecate rand 및 Friends 노력에서 비롯된 것입니다 . 이것은 최근 에 C ++ 14에서 std :: shuffle 메서드가 더 이상 사용되지 않는 이유는 무엇입니까? .
배포판은 플랫폼간에 일관성이 보장되지 않습니다 .
2. std :: ostringstream 또는 sprintf 대신 std :: to_string 사용
C ++ 11은 숫자를 std :: string 으로 변환하는 데 사용할 수있는 std :: to_string 을 제공합니다 . std :: sprintf 와 동일한 내용을 생성합니다 . 대부분의 경우이 중 하나의 장소에서 사용되는 표준 : : ostringstream 나 . 이것은 더 편리합니다. 성능 차이는 많지 않을 것입니다 . C ++ 문서 의 Fast integer to string conversion에서 성능이 주요 관심사 인 경우 훨씬 더 빠른 대안이 있음을 알 수 있습니다.snprintf
#include <iostream>
#include <sstream>
#include <string>
int main()
{
std::ostringstream mystream;
mystream << 100 ;
std::string s = mystream.str();
std::cout << s << std::endl ;
char buff[12] = {0};
sprintf(buff, "%d", 100);
std::string s2( buff ) ;
std::cout << s2 << std::endl ;
std::cout << std::to_string( 100 ) << std::endl ;
}
3. 템플릿 메타 프로그래밍 대신 constexpr 사용
리터럴을 다루는 경우 템플릿 메타 프로그래밍보다 constexpr 함수를 사용하면 더 명확하고 더 빨리 컴파일되는 코드가 생성 될 수 있습니다. 이 기사의 원치 속도? constexpr 메타 프로그래밍을 사용하십시오! 템플릿 메타 프로그래밍을 사용하여 소수 결정의 예를 제공합니다.
struct false_type
{
typedef false_type type;
enum { value = 0 };
};
struct true_type
{
typedef true_type type;
enum { value = 1 };
};
template<bool condition, class T, class U>
struct if_
{
typedef U type;
};
template <class T, class U>
struct if_<true, T, U>
{
typedef T type;
};
template<size_t N, size_t c>
struct is_prime_impl
{
typedef typename if_<(c*c > N),
true_type,
typename if_<(N % c == 0),
false_type,
is_prime_impl<N, c+1> >::type >::type type;
enum { value = type::value };
};
template<size_t N>
struct is_prime
{
enum { value = is_prime_impl<N, 2>::type::value };
};
template <>
struct is_prime<0>
{
enum { value = 0 };
};
template <>
struct is_prime<1>
{
enum { value = 0 };
};
및 constexpr 함수 사용 :
constexpr bool is_prime_recursive(size_t number, size_t c)
{
return (c*c > number) ? true :
(number % c == 0) ? false :
is_prime_recursive(number, c+1);
}
constexpr bool is_prime_func(size_t number)
{
return (number <= 1) ? false : is_prime_recursive(number, 2);
}
constexpr 버전은 템플릿 메타 프로그래밍 구현보다 훨씬 짧고 이해하기 쉬우 며 성능이 훨씬 뛰어납니다.
4. 클래스 멤버 초기화를 사용하여 기본값 제공
최근에 언급 했듯이 선언시 새로운 C ++ 11 멤버 초기화 기능이 초기화 목록을 쓸모 없게 만들었습니까? 클래스 멤버 초기화는 기본값을 제공하는 데 사용할 수 있으며 클래스에 여러 생성자가있는 경우를 단순화 할 수 있습니다.
Bjarne Stroustrup 은 C ++ 11 FAQ에서 좋은 예를 제공합니다.
이렇게하면 타이핑이 약간 절약되지만 실제 이점은 여러 생성자가있는 클래스에서 제공됩니다. 종종 모든 생성자는 멤버에 대해 공통 이니셜 라이저를 사용합니다.
공통 이니셜 라이저가있는 멤버의 예를 제공합니다.
class A {
public:
A(): a(7), b(5), hash_algorithm("MD5"), s("Constructor run") {}
A(int a_val) : a(a_val), b(5), hash_algorithm("MD5"), s("Constructor run") {}
A(D d) : a(7), b(g(d)), hash_algorithm("MD5"), s("Constructor run") {}
int a, b;
private:
HashingFunction hash_algorithm; // Cryptographic hash to be applied to all A instances
std::string s; // String indicating state in object lifecycle
};
그리고 말한다 :
hash_algorithm과 s에 각각 하나의 기본값이 있다는 사실은 복잡한 코드로 인해 손실되며 유지 관리 중에 쉽게 문제가 될 수 있습니다. 대신 데이터 멤버의 초기화를 제거 할 수 있습니다.
class A {
public:
A(): a(7), b(5) {}
A(int a_val) : a(a_val), b(5) {}
A(D d) : a(7), b(g(d)) {}
int a, b;
private:
HashingFunction hash_algorithm{"MD5"}; // Cryptographic hash to be applied to all A instances
std::string s{"Constructor run"}; // String indicating state in object lifecycle
};
C ++ 11에서 클래스 멤버 이니셜 라이저를 사용하는 클래스 는 C ++ 14에서이 제한이 제거되었지만 더 이상 집계 가 아닙니다 .
5. 손으로 굴린 typedef 대신 cstdint에서 고정 너비 정수 유형을 사용
C ++ 11 표준은 C99를 표준 참조로 사용하므로 고정 너비 정수 유형도 얻 습니다. 예를 들면 :
int8_t
int16_t
int32_t
int64_t
intptr_t
일부는 선택 사항이지만 정확한 너비 정수 유형의 경우 C99 섹션의 다음이 7.18.1.1
적용됩니다.
이러한 유형은 선택 사항입니다. 그러나 구현에서 너비가 8, 16, 32 또는 64 비트이고 패딩 비트가없는 정수 유형을 제공하고 (부호화 된 유형의 경우) 2의 보수 표현을 가진 정수 유형을 제공하는 경우 해당 유형 정의 이름을 정의해야합니다.
For-each 구문 :
std::vector<int> container;
for (auto const & i : container)
std::cout << i << std::endl;
Use the uniform initialization syntax for variable initialization
widget w(x); // old
widget w{x}; // new
to avoid problems like c++'s most vexing parse (the rest of the reasons why the new way is superior are explained in the linked article by Herb Sutter)
This blog post proposes the Rule of Zero if all ownerships into a class follow the RAII principle, allowing to get rid of the Rule of Three/Four/Five in C++11.
However, Scott Meyers shows here that not writing explicitly the destructor, copy/move constructors and assignment operators can induce subtle problems if you slightly change your code (say, for debugging). He then recommends to explicitly declare default (C++11 feature) these functions:
~MyClass() = default;
MyClass( const MyClass& ) = default;
MyClass( MyClass&& ) = default;
MyClass& operator=( const MyClass& ) = default;
MyClass& operator=( MyClass&& ) = default;
- Changing
std::map
tostd::unordered_map
andstd::set
tostd::unordered_set
where ever order of container's elements is irrelevant, enhances significantly the performance. - Using
std::map::at
instead of using square bracket syntax insertion, when you want to avoid involuntary insertions. - Use alias templates when you want to
typedef
templates. - Use of initialization lists instead of for loops to initialize STL containers.
- Replace fixed size C arrays with std::array.
Feature: std::move
"Express clear difference between the copying and moving the resources"
std::string tmp("move");
std::vector<std::string> v;
v.push_back(std::move(tmp));
//At this point tmp still be the valid object but in unspecified state as
// its resources has been moved and now stored in vector container.
Optimize simple mathematical functions with constexpr, especially if they are called inside inner loops. This would allow the compiler to calculate them at compilation saving you time
Example
constexpr int fibonacci(int i) {
return i==0 ? 0 : (i==1 ? 1 : fibonacci(i-1) + fibonacci(i-2));
}
Another example is to use std::enable_if
to limit the allowed template parameters types in a particular template function/class. This would make your code safer (in case you haven't use SFINAE to constrains the possible template arguments in your old code) when you implicit assume some property about the template types and it is just one extra line of code
example:
template
<
typename T,
std::enable_if< std::is_abstract<T>::value == false, bool>::type = false // extra line
>
void f(T t)
{
// do something that depends on the fact that std::is_abstract<T>::value == false
}
Update 1: If you have a small array where the size is known at compile time and you you want to avoid the overhead of the heap allocation in std::vector (meaning: you want the array on the stack), you only choice in C++03 was to use c-style arrays. Change that to std::array
. It is a simple change that provides you a lot of the functionally present in std::vector + stack allocation (much faster than heap allocation as I said before).
Prefer scoped enums to unscoped enums
In C++98 the enums, there is no scoped for enums like the following code snippet. The names of such enumerators belong to the scope containing enum, namely nothing else in that scope may have the same name.
enum Color{ blue, green, yellow }; bool blue = false; // error: 'blue' redefinition
However, in C++11, the
scoped enums
can fix this issue.scoped enum
are declared varenum class
.enum class Color{ blue, green, yellow }; bool blue = false; // fine, no other `blue` in scope Color cc = blue; // error! no enumerator `blue` in this scope Color cc = Color::blue; // fine auto c = Color::blue; // fine
The enumerators of
scope enums
are more strongly typed. But, the enumerators ofunscoped enums
implicitly convert to other typesenum Color{ blue, green, yellow }; std::vector<std::size_t> getVector(std::size_t x); Color c = blue; if (c < 10.1) { // compare Color with double !! auto vec = getVector(c); // could be fine !! }
However,
scoped enums
will be failed in this case.enum class Color{ blue, green, yellow }; std::vector<std::size_t> getVector(std::size_t x); Color c = Color::blue; if (c < 10.1) { // error ! auto vec = getVector(c); // error !! }
Fix it through
static_cast
if (static_cast<double>(c) < 10.1) { auto vec = getVector(static_cast<std::size_t>(c)); }
unscoped enums
may be forward-declared.enum Color; // error!! enum class Color; // fine
Both
scoped
andunscoped
enums support specification of the underlying type. The default underlying type forscoped enums
isint
.Unscoped enums
have no default underlying type.
Using Concurrency API
Prefer task-based to thread-based
If you want to run a function
doAsyncWork
asynchronously, you have two basic choices. One is thread-basedint doAsyncWork(); std::thread t(doAsyncWork);
The other is task-based.
auto fut = std::async(doAsyncWork);
Obviously, we can get the return value of
doAsyncWork
through task-based more easily than thread-based. With thetask-based
approach, it’s easy, because the future returned fromstd::async
offers the get function. Theget
function is even more important ifdoAsyncWork
emits an exception, becauseget
provides access to that, too.Thread-based
calls for manual management of thread exhaustion, oversubscription, load balancing, and adaptation to new platforms. ButTask-based
viastd::async
with the default launch policy suffers from none of these drawbacks.
Here are several links:
C/C++ Programming Abstractions for Parallelism and Concurrency
Use smart pointers. Notice that there are still good reason to have naked pointers in some cases, the best way to check if a pointer should be smart is to look for the uses of delete
on it.
There should be no reason to use new
either. Replace every new
with make_shared
or make_unique
.
Unfortunately make_unique
didn't make it in the C++11 standard, the best solution IMO is to implement it yourself(see previous link), and put some macros to check for the __cplusplus
version (make_unique
is available in C++14).
Using make_unique
and make_shared
is really important in order to make your code exception safe.
Use of override keyword
Mark virtual functions in derived classes as override (if they indeed override of course). This can protect against introducing bugs in the future, e.g. by changing the signature of a virtual function in a base class and forgetting to change the signature in all derived classes accordingly.
참고URL : https://stackoverflow.com/questions/21514913/refactoring-with-c-11
'Program Tip' 카테고리의 다른 글
소스 코드에서 인증에 사용되는 비밀번호 처리 (0) | 2020.11.11 |
---|---|
matplotlib imshow () 그래프 축의 값 변경 (0) | 2020.11.11 |
퍼팅 할 때 오류 "React.Children.only 하나의 반작용 자식 요소를받을 것으로 예상" (0) | 2020.11.11 |
C에서 함수를 호출하기 전의 매개 변수 평가 순서 (0) | 2020.11.11 |
Tomcat에서 모든 기본 HTTP 오류 응답 콘텐츠 비활성화 (0) | 2020.11.11 |