스칼라의 연산자 오버로드가 "좋은"것이지만 C ++의 "나쁜"것은 무엇입니까?


155

C ++에서 연산자 오버로딩은 많은 사람들에 의해 A Bad Thing (tm)으로 간주되며, 새로운 언어에서는 반복되지 않는 실수입니다. 확실히, 그것은 자바를 설계 할 때 특별히 떨어 뜨린 기능 중 하나였습니다.

스칼라에 대해 읽기 시작 했으므로 연산자 오버로드와 매우 흡사합니다 (기술적으로는 연산자가없고 함수 만 있기 때문에 연산자 오버로드가 없지만). 그러나 C ++에서 연산자 오버로드와 질적으로 다르지 않은 것으로 보입니다. 여기서 연산자를 특수 함수로 정의합니다.

그래서 제 질문은 스칼라에서 "+"를 정의하는 아이디어가 C ++에서보다 더 나은 아이디어로 만드는 이유는 무엇입니까?


27
스칼라가 아닌 C ++도 모든 프로그래머들 사이에서 보편적 합의에 의해 정의 된 것은 아니다. 어떤 사람들은 C ++에 관심이 있다는 사실과 어떤 사람들은 스칼라에 관심이 없다는 사실 사이에 모순이 없다고 생각합니다.
Steve Jessop

16
C ++에서 연산자 오버로드에 대해 나쁘지 않습니다.
강아지

5
이것은 새로운 것은 아니지만 연산자 오버로드 및 기타 "고급"기능이 문제가 될 때 C ++를 방어하는 방법은 간단합니다. 저는 항상 우리가 어떻게 유능하고 자율적이라고 생각하며 우리를 위해 이런 결정을 할 필요가없는 것을 좋아했습니다.
Elliott

스칼라는 c ++ 이후 수십 년 동안 디자인되었습니다. 프로그래밍 언어 측면에서 그 뒤에있는 사람은 매우 현명합니다. 다른 100 년 동안 c ++ 또는 스칼라를 고수하면 둘 다 나쁘다는 것이 분명 해집니다! 편견은 분명히 본성에 있지만 우리는 싸울 수 있습니다. 기술의 역사를 보면 모든 것이 쓸모 없게됩니다.
Nader Ghanbari

답변:


242

C ++은 C의 진정한 파란색 연산자를 상속합니다. 즉, 6 + 4의 "+"는 매우 특별합니다. 예를 들어, 그 + 함수에 대한 포인터를 얻을 수 없습니다.

반면 스칼라는 그런 식으로 연산자가 없습니다. 단어 이름이 아닌 기호에 대해 메소드 이름과 약간의 우선 순위를 정의 할 때 유연성이 뛰어납니다. 따라서 기술적으로 스칼라에는 운영자 과부하가 없습니다.

호출하고자하는 것이 무엇이든, 연산자 오버로딩은 본질적으로 C ++에서도 나쁘지 않습니다. 문제는 나쁜 프로그래머가 그것을 남용 할 때입니다. 그러나 솔직히 말해서 프로그래머 오버로드를 남용하는 프로그래머의 능력을 빼앗아도 프로그래머가 남용 할 수있는 모든 것을 고치는 버킷이 줄어들지는 않는다고 생각합니다. 진정한 대답은 멘토링입니다. http://james-iry.blogspot.com/2009/03/operator-overloading-ad-absurdum.html

그럼에도 불구하고, C ++의 연산자 오버로딩과 스칼라의 유연한 메소드 이름은 IMHO로 인해 스칼라가 덜 학 대화되고 더 학 대화되도록하는 차이점이 있습니다.

C ++에서 고정 표기법을 얻는 유일한 방법은 연산자를 사용하는 것입니다. 그렇지 않으면 object.message (argument) 또는 pointer-> messsage (argument) 또는 function (argument1, argument2)을 사용해야합니다. 따라서 코드에 특정 DSL 스타일을 원한다면 연산자를 사용해야하는 압력이 있습니다.

스칼라에서는 모든 메시지 전송과 함께 접두사 표기법을 얻을 수 있습니다. "객체 메시지 인수"는 완벽하게 적용됩니다. 즉, 부호가 아닌 단어를 사용할 필요가 없습니다.

C ++ 연산자 오버로딩은 기본적으로 C 연산자로 제한됩니다. "+"및 ">>"와 같은 비교적 적은 수의 기호에 광범위한 관련없는 개념을 매핑하도록 사람들에게 압력을 가하는 조작자 만 사용할 수 있다는 한계와 결합

스칼라는 메서드 이름으로 유효한 비 단어 기호를 광범위하게 허용합니다. 예를 들어, 쓸 수있는 내장 Prolog-ish DSL이 있습니다.

female('jane)!         // jane is female
parent('jane,'john)!   // jane is john's parent
parent('jane, 'wendy)! // jane is wendy's parent

mother('Mother, 'Child) :- parent('Mother, 'Child) & female('Mother) //'// a mother of a child is the child's parent and is female

mother('X, 'john)?  // find john's mother
mother('jane, 'X)?  // find's all of jane's children

:-,!,? 및 & 기호는 일반적인 방법으로 정의됩니다. C ++에서만 & 유효 하므로이 DSL을 C ++에 매핑하려면 이미 다른 개념을 불러 일으키는 몇 가지 기호가 필요합니다.

물론 이것은 또 다른 종류의 학대에 스칼라를 열어줍니다. 스칼라에서는 원한다면 메소드 이름을 $! & ^ %로 지정할 수 있습니다.

스칼라와 같이 단어가 아닌 함수 및 메소드 이름을 사용하는 데 융통성이있는 다른 언어의 경우 스칼라와 같이 모든 "연산자"는 또 다른 방법 인 Haskell과 프로그래머가 융통성있게 명명 된 우선 순위 및 고정도를 정의 할 수있는 스몰 토크를 참조하십시오. 기능.


마지막으로 확인한 결과 3.operator + (5)가 작동했습니다. & (3.operator +)가 그렇지 않다는 것이 정말 놀랍습니다.
Joshua

예를 들어 C ++에서 assert (female ( "jane"))를 수행 할 수 있습니다. 그것은 혼동하지 않을 것입니다-운영자 +는 나쁜 것이 아니라는 바보 같은 프로그래머에 대한 제임스 아이리 게시물로 돌아가십시오.
pm100 1

1
@Joshua 검색 int main() {return (3).operator+(5);}결과error: request for member ‘operator+’ in ‘3’, which is of non-class type ‘int’
zildjohn01

"오퍼레이터 오버로딩은 본질적으로 C ++에서도 나쁘지 않습니다. 문제는 나쁜 프로그래머가 그것을 남용 할 때입니다." 무언가를 사용하여 얻는 이점이 거의 없어서 쉽게 학대받을 수 있다면 전반적인 결과는 코드를 관리하는 다음 사람이 코드의 이상한 부분을 해독하여 생산성을 잃게된다는 것입니다. 그렇지 않은 경우 : 매우 유익하고 잘 작성된 답변.
Jukka Dahlbom

@JukkaDahlbom 스마트 포인터의 존재는 그 자체로 이익을 크게 만듭니다. 그리고 람다, 사용자 정의 숫자 유형, 간격 유형이 있습니다.
Alexey Romanov

66

C ++에서 연산자 오버로드는 많은 사람들이 A Bad Thing (tm)으로 간주합니다

무지한 사람에 의해서만. 그것은 C ++과 같은 언어에서 절대적으로 필요하며, "순수한"관점에서 시작한 다른 언어들이 디자이너들이 필요로하는 것을 알게되면 그것을 추가했다는 것이 눈에 able니다.


30
나는 실제로 Neil에 동의합니다. 변수 / 상수 / 객체 / 인스턴스를 대수 엔티티로 제시하고 사람들이 수학 방식으로 상호 작용을 이해하게하려면 연산자 오버로딩이 필수적입니다. 프로그래밍이 IMHO의 작동 방식이어야합니다.
Massa

16
+1, C ++에서 연산자 오버로드가 좋습니다. 예를 들어 벡터 수학을 훨씬 깨끗하게 만듭니다. 많은 C ++ 기능과 마찬가지로 전원을 신중하게 사용해야합니다.
John Smith

7
@Kristo C ++는 할당하고 복사해야하는 값을 사용하기 때문에. 이를 제어해야하므로 최소한 지정된 유형에 대한 지정 연산자를 지정할 수 있어야합니다.

7
@Kristo : C ++의 의도 중 하나는 사용자 정의 형식이 기본 형식이 수행하는 모든 작업을 수행하도록하는 것입니다 (암시 적 변환과 같은 일부 컨텍스트에서는 다르게 처리되지만). 27 비트 정수를 구현하려면 가능하며 int를 사용하는 것과 같습니다. 연산자 오버로드가 없으면 내장 유형과 동일한 구문으로 UDT를 사용할 수 없으므로 결과 언어는 이러한 의미에서 "C ++과 같지 않습니다".
Steve Jessop

8
"그 방법은 광기에있다"-더 나쁜 방법은 std :: vector <bool>입니다!
Steve Jessop

42

C ++에서 연산자 오버로딩은 보편적으로 나쁜 생각으로 여겨지지 않았습니다. 연산자 오버로딩의 남용은 나쁜 생각으로 여겨졌습니다. 어쨌든 더 자세한 함수 호출로 시뮬레이션 할 수 있기 때문에 언어에서 연산자 오버로드가 실제로 필요하지 않습니다. Java에서 연산자 오버로드를 피하면 Java의 구현 및 사양이 조금 더 단순 해져 프로그래머가 연산자를 남용하지 않아야합니다. Java 커뮤니티에서 운영자 오버로드를 도입하는 것에 대한 토론이있었습니다.

Scala에서 연산자 오버로딩의 장단점은 C ++에서와 동일합니다. 연산자 오버로딩을 적절하게 사용하면보다 자연스런 코드를 작성할 수 있습니다. 그렇지 않은 경우 더 복잡하고 난독 화 된 코드입니다.

참고 : 연산자는 C ++에서 특수 함수로 정의되지 않으며 다른 함수와 같이 작동합니다. 이름 조회에는 몇 가지 차이점이 있지만 멤버 함수 여야하는지 여부와 두 가지 방법으로 호출 할 수 있다는 사실이 있습니다. 1 ) 연산자 구문 및 2) operator-function-id 구문


"어쨌든 더 자세한 함수 호출로 시뮬레이션 할 수 있기 때문에 언어에서 연산자 오버로드가 실제로 필요하지 않습니다." 그 논리에 따라 실제로 연산자 가 필요하지 않습니다 . 왜 사용하지 add(2, multiply(5, 3))않습니까?
Joe Z.

사용되는 일반적인 표기법과 일치하는 경우가 더 많습니다. 수학자 및 물리학자를 고려하면, 연산자의 과부하를 훨씬 더 쉽게 제공하는 C ++ 라이브러리를 이해하고 사용할 수 있습니다. 오히려 프로그래밍 언어보다 방정식에 집중하고 싶습니다.
Phil Wright

19

이 기사- " 긍정적 인 유산 C ++ 및 Java "-귀하의 질문에 직접 답변합니다.

"C ++에는 스택 할당과 힙 할당이 모두 있으므로 모든 상황을 처리하고 메모리 누수를 유발하지 않도록 연산자를 오버로드해야합니다. 실제로는 어렵습니다. 그러나 Java에는 단일 스토리지 할당 메커니즘과 가비지 수집기가있어 연산자 오버로드가 간단합니다." ..

Java는 실수로 (작성자에 따르면) C ++에서는 복잡하기 때문에 연산자 오버로드를 생략했지만 이유를 잊었습니다 (또는 Java에 적용되지 않았다는 것을 알지 못했습니다).

고맙게도 Scala와 같은 고급 언어는 개발자에게 동일한 JVM에서 계속 실행하면서 옵션을 제공합니다.


14
Eckel은 C ++의 합병증으로 인해 연산자 오버로드가 Java에서 버려졌으며 그의 소스가 무엇인지 말하지 않는다는 사상에서 내가 본 유일한 소스입니다. 나는 그것을 할인 할 것입니다. 내가 말한 다른 모든 출처는 잠재적 인 남용으로 인해 버려 졌다고 말합니다. gotw.ca/publications/c_family_interview.htmnewt.com/wohler/articles/james-gosling-ramblings-1.html을 참조하십시오 . 페이지에서 "오퍼레이터 오버로딩"을 검색하십시오.
James Iry

9

연산자 오버로딩에는 아무런 문제가 없습니다. 실제로 숫자 유형에 연산자 오버로드 가 없는 데 문제가 있습니다. BigInteger 및 BigDecimal을 사용하는 일부 Java 코드를 살펴보십시오.

그러나 C ++에는이 기능을 남용하는 전통이 있습니다. 흔히 인용되는 예는 비트 시프트 연산자가 오버로드되어 I / O를 수행하는 것입니다.


<< 및 >> 연산자는 전송 방법을 시각적으로 나타내며 I / O를 수행 하기위한 것이며, 남용이 아니며 표준 라이브러리와 실제적인 것입니다. 그냥 "cin >> something"을 보면 어디로가요? cin에서 무언가로.
peenut

7
@peenut : 그러나 원래의 용도는 조금 바뀌 었습니다. "표준 라이브러리"는 원래 정의를 완전히 엉망으로하는 방식으로 연산자를 사용합니다.
Joe Z.

1
Bjarne Stroustrup (C ++ 제작자)이 C ++ =대신 <<>>초기에 사용하여 실험 했지만 어쨌든 올바른 연산자 우선 순위가 없기 때문에 문제가 발생했습니다 (예 : 검색) 왼쪽 또는 오른쪽의 인수). 그래서 그의 손은 그가 사용할 수있는 것에 약간 묶여있었습니다.
Phil Wright

8

일반적으로 나쁜 것은 아닙니다.
C #과 같은 새로운 언어에도 연산자 오버로드가 있습니다.

연산자 오버로드가 악용되는 것은 잘못된 것입니다.

그러나 C ++에 정의 된 연산자 오버로딩에도 문제가 있습니다. 오버로드 된 연산자는 메소드 호출의 구문 설탕이므로 메소드와 동일하게 작동합니다. 반면에 일반적인 내장 연산자는 메소드처럼 작동하지 않습니다. 이러한 불일치로 인해 문제가 발생할 수 있습니다.

내 헤드 오퍼레이터의 상단 ||&&.
이들의 내장 버전은 바로 가기 연산자입니다. 이는 오버로드 된 버전에는 해당되지 않으며 일부 문제를 일으켰습니다.

+-* / 모두가 작동하는 것과 동일한 유형을 반환한다는 사실 (운영자 승격 후)
오버로드 된 버전은 무엇이든 반환 할 수 있습니다 (여기에서 악용이 설정됩니다. 운영자가 중재자 유형을 반환하기 시작하면 사용자가 예상하지 않은 일이 언덕 아래로 내려갑니다).


8

연산자 오버로딩은 실제로 "필요한"것이 아니지만 Java를 사용할 때 실제로 필요한 지점에 도달하면 손톱을 찢어 타이핑을 멈추는 변명을 할 수 있습니다. .

방금 찾은 코드가 오랫동안 오버플로됩니까? 예, BigInteger와 함께 작동하려면 전체 로트를 다시 입력해야합니다. 변수의 유형을 변경하기 위해 휠을 재발 명해야한다는 더 실망스러운 것은 없습니다.


6

Guy Steele은 기조 연설 "언어 성장"에서 연산자 오버로딩도 Java로해야한다고 주장했다. 비디오와 그 내용이 있으며, 정말 놀라운 연설이다. 첫 두 페이지에 대해 그가 무엇에 대해 이야기하고 있는지 궁금해 할 것입니다. 그러나 계속해서 읽으면 요점을보고 깨달음을 얻습니다. 그리고 그가 그런 연설을 할 수 있다는 사실도 놀랍습니다.

동시에,이 연설은 아마도 스칼라를 포함한 많은 기초 연구에 영감을 주었다. 그것은 모두가 현장에서 일하기 위해 읽어야 할 논문 중 하나입니다.

요컨대, 그의 예제는 주로 BigInteger와 같은 수치 클래스와 이상한 것들에 관한 것이지만 필수는 아닙니다.

그러나 연산자 오버로드를 잘못 사용하면 끔찍한 결과를 초래할 수 있으며, 사용하는 라이브러리를 조금만 연구하지 않고 코드를 읽으려고하면 올바른 사용으로도 문제가 복잡해질 수 있습니다. 하지만 좋은 생각입니까? OTOH, 그러한 라이브러리가 운영자를위한 운영자 치트 시트를 포함시키지 않아야합니까?


4

나는 모든 대답이 이것을 놓쳤다 고 생각합니다. C ++에서는 원하는 모든 연산자를 오버로드 할 수 있지만 평가되는 우선 순위에는 영향을 줄 수 없습니다. 스칼라에는이 문제가 없습니다. IIRC.

그것은 나쁜 생각이기 때문에 우선 순위 문제 외에도 사람들은 운영자에게 실질적인 의미를 내놓으며 가독성을 거의 돕지 않습니다. 스칼라 라이브러리는 특히 매번 암기 해야하는 구피 기호에 좋지 않습니다. 라이브러리 관리자는 모래에 머리를 고수하면서 '한 번만 배우면됩니다'라고 말합니다. 좋아, 이제는 '영리한'저자의 암호 구문 * 내가 사용해야하는 라이브러리 수를 배워야합니다. 글을 읽고 쓸 줄 아는 버전의 연산자를 항상 공급한다는 협약이 있다면 그렇게 나쁘지 않을 것입니다.


1
스칼라는 연산자 우선 순위도 고정되어 있지 않습니까?
skaffman

나는 있다고 생각하지만 훨씬 더 평평합니다. 더 중요한 것은 스칼라는 운영자 기간이 짧다는 것입니다. +,-, *는 IIRC 연산자가 아닌 메서드입니다. 그렇기 때문에 2 + 3 * 2는 8이 아니고 10입니다.
Saem

7
스칼라는 기호의 첫 문자를 기반으로 우선 순위 시스템을 가지고 있습니다. scala> 2 + 3 * 2 res0 : Int = 8
James Iry

3

연산자 오버로딩은 C ++ 발명이 아니 었습니다. Algol IIRC에서 왔으며 Gosling도 일반적으로 나쁜 생각이라고 주장하지 않습니다.


물론, C ++ 화신에서 일반적인 논쟁의 여지를 얻었습니다.
skaffman

5
"일반적인 평판의 공기"란 무엇을 의미합니까? 내가 아는 대부분의 사람들은 연산자 오버로딩 (C ++, C #)을 지원하는 언어를 사용하며 불만을들은 적이 없습니다.
Nemanja Trifunovic

ANSI 이전의 C ++에 대한 오랜 과거의 경험에서 말하고 있으며, 그에 대한 일반적인 싫어하는 점을 분명히 기억합니다. 아마도 ANSI C ++로 상황이 개선되었거나 사람들이 그것을 남용하지 않는 방법을 배웠을 것입니다.
skaffman

1
cfront 시절 (80 년대 중반) 이후로 C ++을 사용해 온 사람으로서 말하면 ISO 표준의 도입이 작업자 과부하에 대한 사람들의 편견에 영향을 미치지 않았다는 것을 확신 할 수 있습니다.

3

C ++에서 잘못 알려진 유일한 것은 [] =를 별도의 연산자로 오버로드 할 수있는 능력이 없다는 것입니다. 아마도 명백한 이유는 아니지만 그만한 가치가있는 이유로 C ++ 컴파일러에서 구현하기가 어려울 수 있습니다.


2

다른 답변들이 지적했듯이; 연산자 과부하 자체가 반드시 나쁘지는 않습니다. 결과 코드를 명백하게 만드는 방식으로 사용될 때 나쁜 점이 있습니다. 일반적으로 그들을 사용할 때 가장 놀라운 일을하지 않아야합니다 (연산자 + 부서가 합리적 클래스 사용에 문제를 일으킬 수 있음) 또는 Scott Meyers가 말합니다 :

클라이언트는 이미 int와 같은 유형의 작동 방식을 알고 있으므로 합리적 일 때마다 유형이 동일한 방식으로 작동하도록 노력해야합니다 . 의심스러운 경우 int와 같이하십시오 . (효과적인 C ++ 3 판 18 항목)

이제 일부 사람들은 boost :: spirit 과 같은 연산자 오버로드를 극단적으로 가져 왔습니다 . 이 수준에서는 구현 방법을 모릅니다. 그러나 원하는 것을 얻기 위해 흥미로운 구문을 만듭니다. 이것이 좋은지 확실하지 않습니다. 좋은 것 같지만 사용하지 않았습니다.


나는 여기에 연산자 오버로드를 주장하거나 반대하지 않고, 사람들을 정당화하기 위해 사람들을 찾고 있지 않습니다.
skaffman

스프린트는 내가 본 최악의 예제 근처에 있지 않습니다. RogueWave 데이터베이스 라이브러리가 무엇을하는지 볼 수 있습니다!

나는 성령이 연산자를 오용한다는 데 동의하지만, 실제로 더 나은 방법을 생각할 수는 없습니다.
Zifre

1
나는 정신이 운영자를 학대하고 있다고 생각하지 않지만 그것을 추진하고 있습니다. 다른 방법이 없다는 데 동의합니다. 기본적으로 C ++ 구문 내에서 DSL을 만듭니다. C ++의 기능과는 거리가 멀다. 예, 훨씬 더 나쁜 예가 있습니다 :) 일반적으로 적절한 곳에서 사용합니다. 보다 쉬운 디버깅 / 로깅을위한 대부분의 스트리밍 연산자. 그리고 심지어 클래스에서 구현 된 메소드로 전달하는 것은 설탕 일뿐입니다.
매트 가격

1
맛 문제입니다. 그러나 파서 결합기 라이브러리는 기능적 언어 로 Spirit 과 매우 유사한 방식으로 연산자에 과부하 를 가하며 아무도 그것에 대해 논쟁하지 않습니다. 일반적인 관점에서이를 설명하는 많은 논문을 찾을 수있는 "내장 된 도메인 특정 언어"의 경우 Google,이 경우 실제 예인 "scala parser combinator"의 경우 Google은 기술적 인 이유가 많이 있습니다. 기능적 언어에서는 결과 구문이 더 좋은 경우가 많습니다. 예를 들어 구문 분석기를 연결하기 위해 >>의 의미를 변경할 필요가 없습니다.
Blaisorblade

2

C ++의 연산자 오버로딩이 나쁘다고 주장하는 기사를 본 적이 없습니다.

사용자 정의 연산자는 언어 사용자에게보다 높은 수준의 표현 성과 유용성을 허용합니다.


1

그러나 C ++에서 연산자 오버로드와 질적으로 다르지 않은 것으로 보입니다. 여기서 연산자를 특수 함수로 정의합니다.

AFAIK, "정상적인"멤버 함수와 비교하여 연산자 함수에는 특별한 것이 없습니다. 물론 오버로드 할 수있는 특정 연산자 세트 만 있지만 그렇게 특별하지는 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.