항목 18 : 실수가 일어날 수 없는 인터페이스를 제공하자.
매개변수 실수 방지
일,월,년을 매개변수로 받는 멤버 함수를 만든다고하자. 세가지 매개변수가 모두 int라면 사용자가 어느나라 사람이느냐에 따라서 일월년의 순서가 헷갈릴 수 있다. 따라서 일,월,년에 해당하는 클래스를 각각 만들고 이를 매개변수로 받는 등으로 하여 헷갈리는 상황을 방지할 수 있다.자원 해제 실수 방지동적할당한 자원을 포인터만 그대로 돌려주는 함수는 좋지 못하다. 사용자에게 할당해제 책임을 맡기게 되기 때문이다. 이를 막기 위해선 스마트 포인터등에 담아서 리턴해주는 것이 좋다. 추가로 단순 할당해제가 아닌 추가적인 자원 해제가 필요하다면 스마트 포인터의 삭제자를 활용하면 된다. 스마트 포인터의 추가적인 이점은 자원의 할당과 해제가 서로 다른 ddl에서 일어나면 문제가 생길 수 있는데 스마트 포인터의 기본 삭제자, 커스텀 삭제자는 자원이 삭제 되는 시점에 할당된 ddl의 코드에서 자원 해제가 되는 것을 보장해준다는점이 있다.
항목 19 : 클래스 설계는 타입 설계처럼 하자.
c++ 클래스는 단순히 데이터를 담고 함수를 제공해주는 기능을 넘어 하나의 타입으로써 작동하는 느낌을 주는 강력한 기능이다. 따라서 클래스를 설계할 때 마치 타입을 설계하듯이 신중하게 설계하면 이러한 이점을 누릴 수 있다. 다음은 클래스를 설계할때 고려해야할 점이다.
생성 및 소멸
대입과 이동
적법한 값의 제약
상속 계통
타입 변환
연산자와 함수
멤버 접근성
예외 안정성(항목 29)
자원 사용
템플릿
항목 20 : 매개변수는 복사 생성보다 상수 객체 참조자를 사용하자.
커스텀 클래스의 매개 변수를 값으로 받는다면 복사 생성을 통해 전달된다. 값으로 받는다는 것은 넘겨준 원본의 수정없이 사용하겠다는것인데 값대신 상수 객체 참조자를 사용하면 훨씬 효율적이다. 만약 원본 객체의 수정을 원한다면 그냥 객체 참조자를 쓰면 된다.
복사 손실
참조자를 매개변수로 쓰는 추가적인 이점은 복사손실을 막는 것이다. 만약 자식 타입의 객체를 부모 타입을 매개변수로 받는 함수에 매개변수로 넘기면 부모 타입에 대한 복사 생성자가 호출되어 객체가 복사된다. 즉 함수에서 받은 매개변수 객체는 완전히 부모 타입이며 virtual함수를 호출하더라도 부모의 함수가 호출된다. 이를 복사 손실이라 한다. 하지만 참조자로 매개변수를 받는다면 부모 타입의 참조자에 자식 타입의 참조가 담겨 virtual 함수 호출시 자식의 것이 정확히 호출된다.
stl 반복자 , 함수 객체
int, 포인터같은 기본 타입이나 그에 준하는 작은 클래스들은 값에 의한 전달이 효율적일때도 있다. 그리고 stl의 반복자와 함수 객체도 이러한 값에 의한 전달방식을 사용하여 매개변수로 받아 구현되어 있다. 따라서 커스텀 반복자나 함수 객체를 만들때는 값에 의한 전달, 즉 복사 생성자를 이용한 전달을 염두에 두고 생성해야한다. 그리고 당연히 이에 따라 복사 손실을 방지하기 위해 virtual함수 생성들에 주의를 기울여야 한다.
작은 클래스
위에서 작은 클래스는 값에 의한 전달도 나쁘지않을 수 있다고 했지만 특별한 이유가 없다면 값에 의한 전달을 쓰자. 컴파일러에 따라서 기본 타입과 커스텀 타입을 아예 구분하여 기본 타입은 레지스터를 사용하지만 커스텀 타입은 절대 쓰지않는 식의 성능 차별이 있을 수 있기에 참조자를 넘기는게 더 효율적인 경우가 있다. 또한 이후에 수정에 의해 크기가 커질 수 있으니 미리 대비해두는 것이 좋기 때문도 있다.
항목 21 : 반환값에는 성능 향상을 위해 참조자를 쓰면 안된다.
항목 20을 보고 그럼 반환값도 성능을 위해 참조자를 반환하면 되지않을까 생각할수도있다. 매개변수로 받은 참조자를 그대로 리턴하는 등의 원래 목적을 위해 참조자를 반환하는 것이 아닌 성능을 위해 로직에 맞지 않게 참조자를 반환하는 것은 좋지 않다. a,b를 받아 곱한 뒤 리턴하는 함수를 생각해보자. a,b를 곱한 결과를 리턴해야하는데 이를 참조자로 반환하려면 동적할당하여 반환하는 수 밖에 없다. 그러면 받은쪽에서 해제의 책임을 가지게 되는데 책임 전가에서부터 좋지않으며 레퍼런스를 받아 해제를 하는 것은 굉장히 좋지않은 코딩 형태가 될 수밖에 없다. 만약 동적할당한 데이터를 리턴하는 것이 진짜 목적이라면 포인터나 스마트 포인터를 리턴하는 것이 맞다. 정리하자면 성능을 향상 시킨답시고 반환값을 참조자로 쓰는 행위는 하지말자. a,b를 곱한 결과를 값으로서 복사 생성으로 전달하자.
항목 22 : 멤버 변수는 private로 하자.
멤버 변수는 private로 해야한다. 사용자가 이 데이터에 접근하려면 멤버 함수를 통해야한다. 이유는 다음과 같다.
1. 일관성
사용자가 데이터에 접근할 때 함수인지 멤버인지 고민할 필요가 없다. a.data인지 a.data()인지 고민할 필요가 없다는 소리이다.2. 접근 제어함수로 데이터에 접근하게 하면 읽기, 쓰기 제한이나 추가적인 로직적인 접근 제한을 자유롭게 걸 수 있다.3. 캡슐화 : 수정 유연성변수를 public으로 열어두면 사용자들이 변수를 직접가져다 사용할 것이다. 이렇게 된 시점에서 변수 구조를 바꾸는 등 클래스 내부 구조를 자유롭게 바꾸지 못한다. 하지만 멤버 함수로만 접근을 허용했다면 내부 구조가 바뀌더라도 인터페이스 함수가 최종적인 결과만 문제 없이 뱉어내도록하며 구조를 수정할 수 있기에 수정 유연성이 훨씬 올라간다.
참고서적
Effective C++
'프로그래밍 언어 > Effective C++' 카테고리의 다른 글
[Effective C++]항목 26~28: 구현 1 (0) | 2023.04.27 |
---|---|
[Effective C++]항목 23~25 : 클래스에 대한 비멤버 함수 (0) | 2023.04.10 |
[Effective C++]항목 13~17 : 자원 관리 (0) | 2023.03.29 |
[Effective C++]항목 9~12 : 생성자, 소멸자 및 대입 연산자 2 (0) | 2023.03.25 |
[Effective C++]항목 5~8 : 생성자, 소멸자 및 대입 연산자 1 (0) | 2023.03.24 |