일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 자료구조
- 너비우선탐색
- 컴퓨터공학과
- 오에스
- vector
- OS
- 오퍼레이팅시스템
- Stack
- Computer science
- 그래프
- 문제풀이
- c++
- 스택
- bfs
- 개발
- 정석학술정보관
- coding
- 구현
- DP
- 코테
- 컴공과
- 컴공
- cs
- 정석
- Operating System
- 북리뷰
- 브루트포스
- 코딩
- 알고리즘
- 백준
- Today
- Total
Little Jay
[C++] Attributes 본문
Clion을 사용하면서 재미있는 부분을 발견했습니다.
오른쪽에 IntelliSense가 추천해주는 코딩 스타일에서 [[nodiscard]]를 사용하는것이 권장된다고 나와있습니다.
함수앞에 [[nodiscard]]를 붙일 수 있는데 이런것을 Attribute라고 합니다.
처음보는 스타일에 당황하긴 했지만 C++ 11부터 이런 스타일을 지원했으며,
컴파일시 최적화 부분에서 이를 유용하게 사용할 수 있겠습니다.
본 포스팅에서는 이 Attribute에 대해 정리 해보겠습니다.
Attribute
직역하면 속성이라는 의미이다.
C++ 전역에서 사용이 가능하며(유형, 변수, 함수, 이름, 코드 블록 앞에서 사용 가능),
이는 컴파일할때 특정 메세지를 생성하거나 컴파일러가 특정 동작을 수행할 수 있게 해줍니다.
이는 필수적으로 붙이는 속성은 아닙니다.
그러나 이를 통해 최적화, 다양한 동작들을 할 수 있다는 점에서 유의미합니다.
Syntax
[[ attr ]]
[[ attr1, attr2, attr3(args) ]]
[[ attr-list ]]
[[ using attr-namespace: attr-list]] // since C++ 17
위와 같이 bracket 두개를 사용합니다.
Attributes들을 열거해서 사용할 수도 있고 특정 namespace안에서 사용할 수 있도록 해줍니다.
[[ noreturn ]] c++11
함수가 아무것도 return하지 않는다는 것을 알려줍니다.
함수의 선언 부분에서만 declare 할 수 있으며, 함수가 실제로 반환되는 경우 warning을 일으킵니다.
여기서 warning이란 컴파일 하는데에는 문제가 없지만, 권장되지는 않는다라는 의미입니다.
이를 통해 컴파일러가 최적화를 수행할 수 있습니다.
호출에 관련된 임시 상태들을 저장하고 불러올 필요가 없습니다.
호출자에게 돌아오지 않기에, 불필요한 블럭 내 하위 코드들을 dead-code로써 지워버릴 수가 있다는 것이죠.
또한 [[ noreturn ]]으로 선언된 함수가 어떠한 값(void포함) 반환하려 할 경우의 결과는 미정(undefined)입니다.
VS에서 DEBUG 모드에서 수행할 경우 아래의 코드가 잘 수행될 수 있지만, r
elease모드에서는 foo와 bar는 실행되지 않습니다.
#include <iostream>
using namespace std;
[[ noreturn ]] void trw() {
throw "error";
}
[[ noreturn ]] void func(int a) {
// do nothing;
// 정상적인 코드
}
[[ noreturn ]] int foo() {
return 1;
// warning 발생
}
int bar [[ noreturn ]] (int n) {
return 2;
// warning 발생
}
int main() {
func(10);
//deadcode
cout << foo();
cout << bar(10);
return 0;
}
[[ carries_dependency ]] c++11
이는 std::memory_order의 종속성 체인이 함수 내부 및 외부로 전파되어 컴파일러가 불필요한 메모리 펜스 명령을 건너뛸 수 있음을 나타냅니다.
cppreference에 이렇게 나와 있어 번역은 했지만 아직 예시같은게 없어 감이 잘 안옵니다.
추후에 수정하겠습니다.
[[deprecated]], [[deprecated("message")]] c++14
사용은 가능하나 권장되지는 않는 attribute입니다.
함수를 사용 하기에 적합 하지 않을 수 있음을 의미합니다.
컴파일러는 클라이언트 코드에서 함수를 호출 하려고 할 때, 사용자에게 warning 메세지를 전달합니다.
위의 "message"로 전달하고 싶은 메세지를 전달할 수 있습니다.
클래스의 선언, typedef 이름, 변수, 비정적 데이터 멤버, 함수, 네임 스페이스, 열거형, 열거자 또는 템플릿 특수화에 적용할 수 있습니다.
라이브러리 제작자들에게 많이 사용될 수 있는 코드입니다.
cppreference 사이트에 예시가 따로 없어 예시는 다른 블로그에서 참조했습니다.
#include <iostream>
using namespace std;
[[deprecated]] void foo(int i) {
if (i > 0)
throw "positive";
}
// note: 'bar' 선언을 참조하십시요
[[deprecated("Will be removed in the next version")]] void bar(int i) {
if (i > 0)
throw "positive";
}
int main()
{
// warning C4996: 'foo': deprecated로 선언되었습니다.
foo(1);
// warning C4996: 'goo': Will be removed in the next version
bar(1);
}
[[ nodiscard ]] c++17
함수의 반환 값이 삭제 되지 않도록 지정합니다.
만약 return값이 버려질 경우 컴파일 경고를 발생시킵니다.
그러나 강제로 이를 void로 형변환을 하게되면 경고가 나타나지 않습니다.
[[ nodiscard ]] int foo(int x) {
return x * 2;
}
int main()
{
// warning C4834: 'nodiscard' 특성이 포함된 함수의 반환 값을 버리는 중
foo(1);
(void)foo(2);
return 0;
}
[[ maybe_unused ]] c++17
[[ nodiscard ]]와는 반대의 개념입니다.
#include <cassert>
[[maybe_unused]] void foo([[maybe_unused]] bool thing1,
[[maybe_unused]] bool thing2) {
[[maybe_unused]] bool bar = thing1 && thing2;
assert(bar); // in release mode, assert is compiled out, and b is unused
// no warning because it is declared [[maybe_unused]]
}
int main() { foo(true, true); }
[[ fallthough ]] c++17
switch문에서 사용가능한 attribute입니다.
이는 case, default 전에 명시되어야합니다.
직역을 하게 되면 '그대로 내려간다' 정도로 해석할 수 있겠습니다'
switch문에서 의도적으로 break를 넣지 않을 수 있는데 컴파일러에서 이를 경고를 출력하게됩니다.
이 속성은 컴파일러에게 그러한 경고를 발생시키지 않겠다고 미리 알려주는 역할을 해줍니다.
아래의 예시를 실행시켜보시면 쉽게 이해가 되실겁니다.
void f(int n) {
void g(), h(), i();
switch (n) {
case 1:
case 2:
g();
[[fallthrough]];
case 3: // no warning on fallthrough
h();
case 4: // compiler may warn on fallthrough
if(n < 3) {
i();
[[fallthrough]]; // OK
}
else {
return;
}
case 5:
while (false) {
[[fallthrough]]; // ill-formed: next statement is not part of the same iteration
}
case 6:
[[fallthrough]]; // ill-formed, no subsequent case or default label
}
}
[[ likely ]], [[unlikely]] c++20
컴파일러가 해당 문을 포함하는 실행 경로가 해당 문을 포함하지 않는 대체 실행 경로보다
더 높거나 더 낮은 경우에 최적화하도록 허용합니다.
아래의 예시를 보시면 바로 이해가 가실겁니다.
i == 1의 경우에는 case 2에 들어가더라도 영향을 미치지 않게 되는 것이죠.
#include <iostream>
using namespace std;
int f(int i)
{
switch (i)
{
case 1:
cout << "fall through excetued and go to case 2";
[[fallthrough]];
[[likely]] case 2: return 1;
}
return 2;
}
int main() {
cout << f(1);
}
unlikely는 그 반대의 개념으로 이해하시면 됩니다.
이러한 likely와 unlikely의 attribute의 특성은 시간적인 측면에서 유의미합니다.
아래의 코드를 보시면 이해가 되실겁니다.
#include <chrono>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <random>
namespace with_attributes {
constexpr double pow(double x, long long n) noexcept {
if (n > 0) [[likely]]
return x * pow(x, n - 1);
else [[unlikely]]
return 1;
}
constexpr long long fact(long long n) noexcept {
if (n > 1) [[likely]]
return n * fact(n - 1);
else [[unlikely]]
return 1;
}
constexpr double cos(double x) noexcept {
constexpr long long precision{16LL};
double y{};
for (auto n{0LL}; n < precision; n += 2LL) {
[[likely]] y += pow(x, n) / (n & 2LL ? -fact(n) : fact(n));
}
return y;
}
} // namespace with_attributes
namespace no_attributes {
constexpr double pow(double x, long long n) noexcept {
if (n > 0)
return x * pow(x, n - 1);
else
return 1;
}
constexpr long long fact(long long n) noexcept {
if (n > 1)
return n * fact(n - 1);
else
return 1;
}
constexpr double cos(double x) noexcept {
constexpr long long precision{16LL};
double y{};
for (auto n{0LL}; n < precision; n += 2LL) {
y += pow(x, n) / (n & 2LL ? -fact(n) : fact(n));
}
return y;
}
} // namespace no_attributes
double gen_random() noexcept {
static std::random_device rd;
static std::mt19937 gen(rd());
static std::uniform_real_distribution<double> dis(-1.0, 1.0);
return dis(gen);
}
volatile double sink{}; // ensures a side effect
int main() {
for (const auto x : {0.125, 0.25, 0.5, 1. / (2 << 25)}) {
std::cout
<< std::setprecision(53)
<< "x = " << x << '\n'
<< std::cos(x) << '\n'
<< with_attributes::cos(x) << '\n'
<< (std::cos(x) == with_attributes::cos(x) ? "equal" : "differ") << '\n';
}
auto benchmark = [](auto fun, auto rem) {
const auto start = std::chrono::high_resolution_clock::now();
for (auto size{1ULL}; size != 10'000'000ULL; ++size) {
sink = fun(gen_random());
}
const std::chrono::duration<double> diff =
std::chrono::high_resolution_clock::now() - start;
std::cout << "Time: " << std::fixed << std::setprecision(6) << diff.count()
<< " sec " << rem << std::endl;
};
benchmark(with_attributes::cos, "(with attributes:)");
benchmark(no_attributes::cos, "(without attributes)");
benchmark(cos, "(std::cos)");
}
[[ no_unique_address ]]
이거를 이해하는데 시간이 좀 걸렸습니다.
https://stackoverflow.com/questions/62784750/what-is-the-new-feature-in-c20-no-unique-address
stackoverflow에서 보다 정확한 이해를 하실 수 있습니다.
메모리 최적화를 수행합니다.
c++에서는 0을 허용하지 않습니다.
이것이 어떤 말인가 하면, 어떤 data에 대해서 항상 sizeof(obj)는 0이상이 됩니다.
이게 어떤 문제를 야기하게 되나면,
첫 번째로는 stateless objects(멤버가 없는 classes/structs)가 사용될 경우에 메모리가 낭비되고,
다음으로는 size가 0인 빈 배열을 선언할 수 없습니다.
물론 std::array를 사용하면 두 번째의 배열 문제는 해결할 수 있습니다.
이때 [[ no_unique_address ]]는 이 문제를 해결하게 되는것이죠.
물론 이는 근본적인 해결책이라고는 할 수 없습니다.
사용자의 요청이 있을때만 사용할 수 있는 attribute이기 때문이죠.
아래의 코드를 보시면 이해가 쉽게 되실겁니다.
struct Empty {}; // empty class
struct X {
int i;
Empty e;
};
struct Y {
int i;
[[no_unique_address]] Empty e;
};
int main()
{
// 빈 클래스라고 할지라고 항상 size는 1 이상이 됩니다.
static_assert(sizeof(Empty) >= 1);
// 그렇기 떄문에 여기서 에러가 발생되지 않습니다.
static_assert(sizeof(X) >= sizeof(int) + 1);
// [[no_unique_address]]를 통해 메모리 최적화를 해줄 수 있습니다.
std::cout << "sizeof(Y) == sizeof(int) is " << std::boolalpha
<< (sizeof(Y) == sizeof(int)) << '\n';
}
마무리
Attribute는 필수적인 조건이 아닙니다.
이러한 것이 있다 정도로 이해하시고 최신버전에 맞는 코딩을 하고싶다면 참조해도 될것 같습니다.
감사합니다.
출처
https://en.cppreference.com/w/cpp/language/attributes
http://egloos.zum.com/sweeper/v/3201377
'Univ > Study' 카테고리의 다른 글
[OS] pthread.h 실행 명령어 (0) | 2022.05.10 |
---|---|
객체 지향의 원칙들(SOLID) (0) | 2022.03.04 |
[C++] for 문을 사용할때 조건식에서 주의해야 할 점(unsigned와 size_type 그리고 overflow) (0) | 2022.02.17 |
[C++] emplace_back()과 push_back(), 그리고 Clang-Tidy Bug (0) | 2022.02.16 |
Objected-Oriented Programming의 기둥 (0) | 2022.01.27 |